diff --git a/GAMS/IntExtOpt.gms b/GAMS/IntExtOpt.gms
index b6fa94121b34c0bf7c00657a39228c66be6b376a..eb9064de580b10741c1ebf142a1ceaf87349c00b 100644
--- a/GAMS/IntExtOpt.gms
+++ b/GAMS/IntExtOpt.gms
@@ -1,369 +1,364 @@
-
- SET all_types / monogastrics, ruminants, cereals, oilcropspulses, wheat, maize, rice, oilcrops, pulses, starchyRoots, fruitveg, sugar, energycrops, pasture, setaside/;
-
- SET crop(all_types)                                             / wheat, maize, rice, oilcrops, pulses, starchyRoots, fruitveg, sugar, energycrops, pasture, setaside /;
- SET crop_less_pasture(crop)                                    / wheat, maize, rice, oilcrops, pulses, starchyRoots, fruitveg, sugar, energycrops,          setaside/;
- SET cereal_crop(crop)                                          / wheat, maize, rice /;
- SET oilpulse_crop(crop)                                                            / oilcrops, pulses /;
- SET feed_crop(crop)                                            / wheat, maize, rice, oilcrops, pulses, starchyRoots, fruitveg,                     pasture/;
- SET feed_crop_less_pasture(feed_crop)                          / wheat, maize, rice, oilcrops, pulses, starchyRoots, fruitveg /;
- SET import_crop(all_types)   / monogastrics, ruminants,           wheat, maize, rice, oilcrops, pulses, starchyRoots, fruitveg, sugar, energycrops/;
- SET market_crop(crop) / wheat, maize, rice, oilcrops, pulses, starchyRoots, fruitveg, sugar, energycrops/;
-
- SET land_cover / cropland, pasture, timberForest, carbonForest, natural /;
- SET forested(land_cover) / timberForest, carbonForest, natural /;
- SET managed_forest(land_cover) / timberForest, carbonForest /;
- SET non_timber_forest(land_cover) / cropland, pasture, carbonForest, natural /;
- SET exc_managed_forest(land_cover)/ cropland, pasture, natural /;
- SET exc_natural(land_cover) / cropland, pasture, timberForest, carbonForest /;
- ALIAS (land_cover, land_cover_before);
- ALIAS (land_cover, land_cover_after);
-
- SET location;
- PARAMETER suitableLandArea(location)            areas of land in Mha;
- PARAMETER previousCropArea(crop, location)      areas for previous timestep in Mha;
- PARAMETER previousFertIntensity(crop, location);
- PARAMETER previousIrrigIntensity(crop, location);
- PARAMETER previousOtherIntensity(crop, location);
- PARAMETER previousRuminantFeed(crop);
- PARAMETER previousMonogastricFeed(crop);
- PARAMETER previousImportAmount(all_types);
- PARAMETER previousExportAmount(all_types);
- PARAMETER yieldNone(crop, location)         yield in t per ha;
- PARAMETER yieldFertOnly(crop, location)     yield in t per ha;
- PARAMETER yieldIrrigOnly(crop, location)    yield in t per ha;
- PARAMETER yieldBoth(crop, location)         yield in t per ha;
- PARAMETER yieldShock(crop, location)        rate of yield shock;
- PARAMETER fertParam(crop, location)         yield response to fertilizer parameter;
- PARAMETER irrigParam(crop, location)        yield response to irrigation parameter;
- PARAMETER demand(all_types)                   in t;
- PARAMETER exportPrices(all_types)         prices for exports;
- PARAMETER importPrices(all_types)         prices for imports;
- PARAMETER maxNetImport(import_crop)         maximum net import for each crop based on world market;
- PARAMETER minNetImport(import_crop)         minimum net import for each crop based on world market;
- PARAMETER irrigCost(location)               irrigation cost in cost per 1000 Mlitre or Mha for each litre per m2;
- PARAMETER irrigMaxRate(crop, location)      max water application rate irrigation in litre per m2;
- PARAMETER irrigConstraint(location)         max water available for irrigation in litre per m2;
- PARAMETER minDemandPerCereal(cereal_crop)   min demand for each cereal crop as factor of all cereals;
- PARAMETER minDemandPerOilcrop(oilpulse_crop) min demand for oilcrop pulses as factor of total;
- PARAMETER seedAndWasteRate(all_types)       rate of use for seed and waste combined;
- PARAMETER subsidyRate(crop)                 rates of subsidy compared to costs;
- 
- PARAMETER previousLandCoverArea(land_cover, location) land cover area in Mha;
-
- PARAMETER carbonFluxRate(land_cover_before, land_cover_after, location) carbon flux - MtC-eq per Mha;
- PARAMETER carbonFluxRateNEE(land_cover, location);
- PARAMETER woodYieldRota(location);
- PARAMETER woodYieldLUC(land_cover_before, land_cover_after, location);
- 
- PARAMETER conversionCost(land_cover, land_cover) cost of converting from one land cover to another;
- PARAMETER infrastructureCost(location);
-
- SCALAR meatEfficency                        efficiency of converting feed and pasture into animal products;
- SCALAR fertiliserUnitCost                   fert cost at max fert rate;
- SCALAR otherIParam                          yield response to other intensity;
- SCALAR otherICost                           cost of other intensity;
- SCALAR unhandledCropRate                    rate of fruit veg and other crops not modelled;
- SCALAR setAsideRate                         rate of set aside and failed crop;
- SCALAR domesticPriceMarkup                  factor price increased from cost of production;
- SCALAR maxLandExpansionRate                 max rate of country land expansion;
-
- SCALAR woodDemand;
- SCALAR carbonPrice                                 price of carbon - $1000 per tonne;
- SCALAR woodExportPrice                             price of wood export - $1000 per tC-eq;
- SCALAR woodImportPrice                             price of wood import - $1000 per tC-eq;
- PARAMETER forestRotationPeriod(location);
- SCALAR woodMaxNetImport;
- SCALAR woodMinNetImport;
- SCALAR forestEstablishmentCost;
- SCALAR vegClearingCostRate cost of clearing vegetation $1000 per tC;
-
-*$gdxin "C:\Users\Bart\Documents\PhD\GAMS testing area\_gams_java_gdb1.gdx"
-$gdxin %gdxincname%
-$load location, suitableLandArea, demand
-$load previousCropArea, previousFertIntensity, previousIrrigIntensity, previousOtherIntensity, previousRuminantFeed, previousMonogastricFeed, previousImportAmount, previousExportAmount
-$load yieldNone, yieldFertOnly, yieldIrrigOnly, yieldBoth, yieldShock
-$load fertParam, irrigParam, otherIParam, exportPrices, importPrices, maxNetImport, minNetImport, unhandledCropRate, setAsideRate, maxLandExpansionRate, subsidyRate
-$load meatEfficency, otherICost, irrigCost, irrigMaxRate, irrigConstraint, fertiliserUnitCost, domesticPriceMarkup, minDemandPerCereal, minDemandPerOilcrop, seedAndWasteRate
-$load previousLandCoverArea, carbonFluxRate, carbonPrice, woodExportPrice, woodImportPrice
-$load conversionCost, woodYieldRota, forestRotationPeriod, woodMaxNetImport, woodMinNetImport, woodDemand
-$load forestEstablishmentCost, vegClearingCostRate, carbonFluxRateNEE, woodYieldLUC
-$gdxin
-
- SCALAR delta "use to smooth power function see 7.5 www.gams.com dd docs solversconopt.pdf" / 0.00000000001 /;
-
- demand(cereal_crop) = demand('cereals') * minDemandPerCereal(cereal_crop);
- demand(oilpulse_crop) = demand('oilcropspulses') * minDemandPerOilcrop(oilpulse_crop);
-
- previousCropArea(crop_less_pasture, location) = previousCropArea(crop_less_pasture, location) * (1.0 - unhandledCropRate);
- 
- 
- PARAMETER maxExport(import_crop);
- PARAMETER minExport(import_crop);
-
- PARAMETER cropDM(crop)  kg DM per kg of feed the conversion from feed to meat is done in the R animal product index.  Pasture is in DM terms already
-          /   wheat         0.87
-              maize         0.86
-              rice          0.89
-              oilcrops      0.96
-              pulses        0.31
-              starchyRoots  0.25
-              fruitveg      0.1
-              sugar         1
-              pasture       1    / ;
-
- PARAMETER prodCost(crop)  cost per ha before intensity values not including fertiliser or irrigation i.e. seed spray and other.  In 1000 $ per ha
-          /   wheat             0.32
-              maize             0.31
-              rice              0.36
-              oilcrops          0.2
-              pulses            0.31
-              starchyRoots      3.14
-              fruitveg          4.0
-              sugar             3.0
-              energyCrops       0.34 / ;
-
- PARAMETER baseCost(crop);
- PARAMETER otherIntCost(crop);
- baseCost(crop) = prodCost(crop)*0.3;
- otherIntCost(crop) = baseCost(crop)*0.7 + otherICost;
- baseCost('pasture') = 0.04;
- otherIntCost('pasture') = 0.8 + otherICost;
- 
- maxNetImport(import_crop) = min(maxNetImport(import_crop), demand(import_crop));
- minNetImport(import_crop) = min(minNetImport(import_crop), demand(import_crop));
-
- VARIABLES
-       cropArea(crop, location)           total area for crops - Mha
-       fertI(crop, location)              fertilizer intensity for each crop - factor between 0 and 1
-       irrigI(crop, location)             irrigation intensity for each crop - factor between 0 and 1
-       otherIntensity(crop, location)
-       ruminantFeed(crop)                 amount of feed for ruminant animals - Mt
-       monogastricFeed(crop)              amount of feed for monogatric animals - Mt
-       importAmount(all_types)            imports of crops and meat - Mt
-       exportAmount(all_types)            exports of crops and meat - Mt
-       yield(crop, location)              yield per area for each crop - t per ha
-       unitCost(crop, location)           cost per area for each crop - cost
-       totalFeedDM                        total feed dry matter
-       landCoverArea(land_cover, location)  land cover area in Mha
-       landCoverChange(land_cover_before, land_cover_after, location) land cover change in Mha
-       totalConversionCost(location)      land cover conversion cost - $1000 per ha
-       woodExported                       total wood sold - Mt C-eq
-       woodImported
-       woodSupply
-       woodProdCost
-       vegClearningCost(location)
-       carbonFlux(location)               total carbon flux  - Mt C
-       supply(all_types)
-*       A                                  "artificial variable for debugging https://www.gams.com/blog/2017/07/misbehaving-model-infeasible/"
-       total_cost                         total cost of domestic supply including net imports;
-
- POSITIVE VARIABLE cropArea, fertI, irrigI, otherIntensity, ruminantFeed, monogastricFeed, importAmount, exportAmount,
-                   totalFeedDM, landCoverArea, landCoverChange, totalConversionCost, woodHarvestNat, woodHarvestLuc, woodExported, woodImported,
-                   supply, woodSold, woodSupply, exportAmount, forestRotaHarvest, woodProdCost, vegClearningCost;
-
-* POSITIVE VARIABLE A;
-
- EQUATIONS
-       UNIT_COST_EQ(crop, location)                     cost per area - $1000 per ha or $billion per Mha
-       YIELD_EQ(crop, location)                         yield given chosen intensity - tonnes per hectare
-       MAX_FERT_INTENSITY_CONSTRAINT(crop, location)    constraint on maximum fertilizer intensity
-       MAX_IRRIG_INTENSITY_CONSTRAINT(crop, location)   constraint on maximum irrigation intensity
-       MAX_OTHER_INTENSITY_CONSTRAINT(crop, location)
-       IRRIGATION_CONSTRAINT(location)                  constraint on water usage
-       
-       SUPPLY_CALC(crop)
-       RUMINANTS_SUPPLY_CALC
-       MONOGASTRICS_SUPPLY_CALC
-       TOTAL_NON_PASTURE_FEED_DM_CALC                   calc total feed dry matter not including pasture
-       FEED_MIX_CONSTRAINT(feed_crop_less_pasture)      limit amount of feed for each feed crop
-       SUPPLY_CONSTRAINT(import_crop)
-
-       MAX_NET_IMPORT_CONSTRAINT(import_crop)           constraint on max net imports
-       MIN_NET_IMPORT_CONSTRAINT(import_crop)           constraint on min net imports
-       PASTURE_IMPORT_CONSTRAINT                        constraint to not import pasture
-       PASTURE_EXPORT_CONSTRAINT
-
-
-       SETASIDE_AREA_CALC(location)
-       CROPLAND_LAND_COVER_CALC(location)               cropland area equals sum of all crop areas
-       PASTURE_LAND_COVER_CALC(location)                pasture area (land cover) equals pasture area (land use)
-       LAND_COVER_CHANGE_CALC(land_cover, location)     "calc land cover change. landCoverChange(A, A, location) defined as unchanged land cover"
-       LAND_COVER_CHANGE_CONSTRAINT(land_cover, location) conservation of land area
-       CONVERSION_COST(location)                          cost of land cover conversion
-
-       WOOD_SUPPLY_CALC
-       WOOD_SUPPLY_CONSTRAINT
-       WOOD_MIN_TRADE_CONSTRAINT                          constraint on minimum wood export
-       WOOD_MAX_TRADE_CONSTRAINT
-       WOOD_PROD_COST_CALC
-       VEG_CLEARING_COST_CALC(location)
- 
-       CARBON_FLUX_CALC(location)                         calc carbon flux      
-
-       COST_CALC         Net Present Value function;
-
-**************** Crop cost **************************
-
- UNIT_COST_EQ(crop, location) .. unitCost(crop, location) =E=  (     baseCost(crop) +
-                                                                     fertiliserUnitCost * fertI(crop, location) +
-                                                                     irrigCost(location) * irrigMaxRate(crop, location) * irrigI(crop, location) +
-                                                                     otherIntCost(crop) * otherIntensity(crop, location)
-                                                                     );
-                                                                  
-**************** Crop yields *******************
-
- YIELD_EQ(crop, location) .. yield(crop, location) =E= (
-               yieldNone(crop, location) +
-               (yieldFertOnly(crop, location) - yieldNone(crop, location)) * (1 - exp(-fertParam(crop, location)*fertI(crop, location))) +
-               (yieldIrrigOnly(crop, location) - yieldNone(crop, location)) * (1 - exp(-irrigParam(crop, location)*irrigI(crop, location))) +
-               (yieldBoth(crop, location) + yieldNone(crop, location) - yieldFertOnly(crop, location) - yieldIrrigOnly(crop, location)) *
-                                      (1 - exp(-fertParam(crop, location)*fertI(crop, location))) * (1 - exp(-irrigParam(crop, location)*irrigI(crop, location)))
-            ) * (1 - exp(-otherIntensity(crop, location)*otherIParam));
-            
- MAX_FERT_INTENSITY_CONSTRAINT(crop, location) .. fertI(crop, location) =L= 1;
- MAX_IRRIG_INTENSITY_CONSTRAINT(crop, location) .. irrigI(crop, location) =L= 1;
- MAX_OTHER_INTENSITY_CONSTRAINT(crop, location) .. otherIntensity(crop, location) =L= 1;
- 
- IRRIGATION_CONSTRAINT(location) .. irrigConstraint(location) * suitableLandArea(location) * (1.0 - unhandledCropRate) =G= sum(crop, irrigMaxRate(crop, location) * irrigI(crop, location) * cropArea(crop, location));
-
-*************** Commodity supply *************************
-
- SUPPLY_CALC(crop) .. supply(crop) =E= sum(location, cropArea(crop, location) * yield(crop, location)) * (1 - seedAndWasteRate(crop)) - ruminantFeed(crop) - monogastricFeed(crop);
- 
- RUMINANTS_SUPPLY_CALC .. supply('ruminants') =E= meatEfficency * sum(feed_crop, ruminantFeed(feed_crop) * cropDM(feed_crop)) * (1 - seedAndWasteRate('ruminants'));
- 
- MONOGASTRICS_SUPPLY_CALC .. supply('monogastrics') =E= meatEfficency * sum(feed_crop_less_pasture, monogastricFeed(feed_crop_less_pasture) * cropDM(feed_crop_less_pasture)) * (1 - seedAndWasteRate('monogastrics'));
-
- TOTAL_NON_PASTURE_FEED_DM_CALC .. totalFeedDM =E= sum(feed_crop_less_pasture, (ruminantFeed(feed_crop_less_pasture) + monogastricFeed(feed_crop_less_pasture)) * cropDM(feed_crop_less_pasture));
- FEED_MIX_CONSTRAINT(feed_crop_less_pasture) .. (ruminantFeed(feed_crop_less_pasture) + monogastricFeed(feed_crop_less_pasture)) * cropDM(feed_crop_less_pasture) =L= totalFeedDM * 0.7;
-
- SUPPLY_CONSTRAINT(import_crop) .. supply(import_crop) =E= demand(import_crop) + exportAmount(import_crop) - importAmount(import_crop);
-
-*************** Exports *******************************
-
- MAX_NET_IMPORT_CONSTRAINT(import_crop) .. importAmount(import_crop) - exportAmount(import_crop) =L= maxNetImport(import_crop);
- MIN_NET_IMPORT_CONSTRAINT(import_crop) .. importAmount(import_crop) - exportAmount(import_crop) =G= minNetImport(import_crop);
- PASTURE_IMPORT_CONSTRAINT .. importAmount('pasture') =E= 0;
- PASTURE_EXPORT_CONSTRAINT ..  exportAmount('pasture') =E= 0;
-
-************** Land Cover *****************************
-
- SETASIDE_AREA_CALC(location) .. cropArea('setaside', location) =E= sum(crop_less_pasture, cropArea(crop_less_pasture, location)) * setAsideRate;
- 
- CROPLAND_LAND_COVER_CALC(location) .. landCoverArea('cropland', location) =E= sum(crop_less_pasture, cropArea(crop_less_pasture, location)) / (1.0 - unhandledCropRate);
- 
- PASTURE_LAND_COVER_CALC(location) .. landCoverArea('pasture', location) =E= cropArea('pasture', location);
-
- LAND_COVER_CHANGE_CALC(land_cover, location) .. landCoverArea(land_cover, location) =E= previousLandCoverArea(land_cover, location) +
-                                                                                         sum(land_cover_before, landCoverChange(land_cover_before, land_cover, location)) -
-                                                                                         sum(land_cover_after, landCoverChange(land_cover, land_cover_after, location));
-
-
- LAND_COVER_CHANGE_CONSTRAINT(land_cover, location) .. sum(land_cover_after, landCoverChange(land_cover, land_cover_after, location)) =E= previousLandCoverArea(land_cover, location);
-
- CONVERSION_COST(location) .. totalConversionCost(location) =E= sum((land_cover_before, land_cover_after), landCoverChange(land_cover_before, land_cover_after, location) * conversionCost(land_cover_before, land_cover_after));
-
-************* Forestry ***********************************
-
- WOOD_SUPPLY_CALC .. woodSupply =E= sum(location, landCoverArea('timberForest', location) * woodYieldRota(location) / forestRotationPeriod(location));
- 
- WOOD_SUPPLY_CONSTRAINT .. woodSupply =E= woodDemand + woodExported - woodImported;
-
- WOOD_MIN_TRADE_CONSTRAINT .. woodImported - woodExported =L= woodMaxNetImport;
-
- WOOD_MAX_TRADE_CONSTRAINT .. woodImported - woodExported =G= woodMinNetImport;
- 
- WOOD_PROD_COST_CALC .. woodProdCost =E= sum(location, landCoverArea('timberForest', location) * forestEstablishmentCost / forestRotationPeriod(location));
- 
- VEG_CLEARING_COST_CALC(location) .. vegClearningCost(location) =E= sum((land_cover_before, land_cover_after),
-                                     landCoverChange(land_cover_before, land_cover_after, location) * woodYieldLUC(land_cover_before, land_cover_after, location)) * vegClearingCostRate;
- 
-*********** Carbon fluxes ***********************************
-
- CARBON_FLUX_CALC(location) .. carbonFlux(location) =E= sum((land_cover_before, land_cover_after),
-                landCoverChange(land_cover_before, land_cover_after, location) * carbonFluxRate(land_cover_before, land_cover_after, location)) +
-                sum(land_cover, landCoverArea(land_cover, location) * carbonFluxRateNEE(land_cover, location));
-
-************ Net Present Value ******************************
-
- COST_CALC .. total_cost =E= sum((crop, location), cropArea(crop, location) * unitCost(crop, location) * (1-subsidyRate(crop))) * domesticPriceMarkup +
-                       sum(location, totalConversionCost(location)) +
-                       sum(import_crop, importPrices(import_crop) * importAmount(import_crop)) +
-                       woodImported * woodImportPrice -
-                       sum(import_crop, exportPrices(import_crop) * exportAmount(import_crop)) -
-                       woodExported * woodExportPrice +
-                       woodProdCost +
-                       sum(location, vegClearningCost(location));
-                       
-                  
-
-
- MODEL LAND_USE /ALL/ ;
- fertI.L(crop, location) = previousFertIntensity(crop, location);
- irrigI.L(crop, location) = previousIrrigIntensity(crop, location);
- otherIntensity.L(crop, location) = previousOtherIntensity(crop, location);
- cropArea.L(crop, location) = previousCropArea(crop, location);
- landCoverArea.L(land_cover, location) = previousLandCoverArea(land_cover, location);
- landCoverChange.L(land_cover, land_cover, location) = previousLandCoverArea(land_cover, location);
- ruminantFeed.L(feed_crop) = previousRuminantFeed(feed_crop);
- monogastricFeed.L(feed_crop) = previousMonogastricFeed(feed_crop);
- importAmount.L(all_types) = previousImportAmount(all_types);
- exportAmount.L(all_types) = previousExportAmount(all_types);
-
-* LAND_USE.OptFile = 1;
-
-* display landCoverChange.L
-
- SOLVE LAND_USE USING NLP MINIMIZING total_cost;
-* SOLVE LAND_USE USING NLP MAXIMIZING npv;
-
- display previousCropArea, irrigMaxRate, otherIntensity.L, fertI.L, irrigI.L, cropArea.L;
- display supply.l, demand, ruminantFeed.l, monogastricFeed.l, exportAmount.l;
-
-* Calculate summary information used in Java process
- parameter totalProd(all_types);
- parameter totalProdCost(all_types);
- parameter totalArea(crop);
- parameter totalCropland(location);
- parameter netImportAmount(all_types);
- parameter netImportCost(all_types);
- parameter feedCostRate(feed_crop);
- parameter productionShock(all_types);
- scalar netCarbonFlux;
- scalar netWoodImport;
- scalar totalWoodHarvest;
-
-* Production quantities based on smaller area (before unhandledCropArea adjustment applied)
- totalProd(crop) = sum(location, cropArea.l(crop, location) * yield.l(crop, location));
- productionShock(crop) = sum(location, cropArea.l(crop, location) * yield.l(crop, location) * yieldShock(crop, location));
- totalProd('ruminants') = meatEfficency*(sum(feed_crop, ruminantFeed.l(feed_crop) * cropDM(feed_crop)));
- totalProd('monogastrics') = meatEfficency*(sum(feed_crop, monogastricFeed.l(feed_crop) * cropDM(feed_crop)));
-
-* Cost based on adjusted area
- cropArea.l(crop_less_pasture, location) = cropArea.l(crop_less_pasture, location) / (1.0 - unhandledCropRate);
- totalProdCost(crop) = sum(location, unitCost.l(crop, location) * cropArea.l(crop, location));
- totalCropland(location) = sum(crop_less_pasture, cropArea.l(crop_less_pasture, location));
- totalArea(crop) = sum(location, cropArea.l(crop, location));
-
- feedCostRate(feed_crop)$[totalProd(feed_crop) <> 0] = totalProdCost(feed_crop) / totalProd(feed_crop);
- totalProdCost('ruminants') = sum(feed_crop, ruminantFeed.l(feed_crop) * feedCostRate(feed_crop));
- totalProdCost('monogastrics') = sum(feed_crop, monogastricFeed.l(feed_crop) * feedCostRate(feed_crop));
-
- netImportAmount(import_crop) = importAmount.l(import_crop) - exportAmount.l(import_crop);
- netImportCost(import_crop) = importAmount.l(import_crop) * importPrices(import_crop) - exportAmount.l(import_crop) * exportPrices(import_crop);
-
-
- netCarbonFlux = SUM(location, carbonFlux.L(location));
- netWoodImport = woodImported.L - woodExported.L;
-* netWoodImport = 0;
- totalWoodHarvest = woodSupply.L;
-
- Scalar totalCostsLessLU;
-
- totalCostsLessLU = sum(crop,totalProdCost(crop))+ sum(import_crop,netImportCost(import_crop));
-
-
- Scalar ms 'model status', ss 'solve status';
- ms=LAND_USE.modelstat;
- ss=LAND_USE.solvestat;
+
+ SET all_types / monogastrics, ruminants, cereals, oilcropspulses, wheat, maize, rice, oilcrops, pulses, starchyRoots, fruitveg, sugar, energycrops, pasture, setaside/;
+
+ SET crop(all_types)                                             / wheat, maize, rice, oilcrops, pulses, starchyRoots, fruitveg, sugar, energycrops, pasture, setaside /;
+ SET crop_less_pasture(crop)                                    / wheat, maize, rice, oilcrops, pulses, starchyRoots, fruitveg, sugar, energycrops,          setaside/;
+ SET cereal_crop(crop)                                          / wheat, maize, rice /;
+ SET oilpulse_crop(crop)                                                            / oilcrops, pulses /;
+ SET feed_crop(crop)                                            / wheat, maize, rice, oilcrops, pulses, starchyRoots, fruitveg,                     pasture/;
+ SET feed_crop_less_pasture(feed_crop)                          / wheat, maize, rice, oilcrops, pulses, starchyRoots, fruitveg /;
+ SET import_crop(all_types)   / monogastrics, ruminants,           wheat, maize, rice, oilcrops, pulses, starchyRoots, fruitveg, sugar, energycrops/;
+
+ SET land_cover / cropland, pasture, timberForest, carbonForest, natural /;
+ SET managed_forest(land_cover) / timberForest, carbonForest /;
+ ALIAS (land_cover, land_cover_before);
+ ALIAS (land_cover, land_cover_after);
+
+ SET location;
+ PARAMETER suitableLandArea(location)            areas of land in Mha;
+ PARAMETER previousCropArea(crop, location)      areas for previous timestep in Mha;
+ PARAMETER previousFertIntensity(crop, location);
+ PARAMETER previousIrrigIntensity(crop, location);
+ PARAMETER previousOtherIntensity(crop, location);
+ PARAMETER previousRuminantFeed(crop);
+ PARAMETER previousMonogastricFeed(crop);
+ PARAMETER previousImportAmount(all_types);
+ PARAMETER previousExportAmount(all_types);
+ PARAMETER yieldNone(crop, location)         yield in t per ha;
+ PARAMETER yieldFertOnly(crop, location)     yield in t per ha;
+ PARAMETER yieldIrrigOnly(crop, location)    yield in t per ha;
+ PARAMETER yieldBoth(crop, location)         yield in t per ha;
+ PARAMETER yieldShock(crop, location)        rate of yield shock;
+ PARAMETER fertParam(crop, location)         yield response to fertilizer parameter;
+ PARAMETER irrigParam(crop, location)        yield response to irrigation parameter;
+ PARAMETER demand(all_types)                 in t;
+ PARAMETER exportPrices(all_types)           prices for exports;
+ PARAMETER importPrices(all_types)           prices for imports;
+ PARAMETER maxNetImport(import_crop)         maximum net import for each crop based on world market;
+ PARAMETER minNetImport(import_crop)         minimum net import for each crop based on world market;
+ PARAMETER irrigCost(location)               irrigation cost in cost per 1000 Mlitre or Mha for each litre per m2;
+ PARAMETER irrigMaxRate(crop, location)      max water application rate irrigation in litre per m2;
+ PARAMETER irrigConstraint(location)         max water available for irrigation in litre per m2;
+ PARAMETER minDemandPerCereal(cereal_crop)   min demand for each cereal crop as factor of all cereals;
+ PARAMETER minDemandPerOilcrop(oilpulse_crop) min demand for oilcrop pulses as factor of total;
+ PARAMETER seedAndWasteRate(all_types)       rate of use for seed and waste combined;
+ PARAMETER subsidyRate(crop)                 rates of subsidy compared to costs;
+ 
+ PARAMETER previousLandCoverArea(land_cover, location)                      land cover area in Mha;
+ PARAMETER carbonFluxRateLUC(land_cover_before, land_cover_after, location) carbon flux from land use change - MtC-eq per Mha;
+ PARAMETER carbonFluxRateNEE(land_cover, location)                          net ecosystem exchange carbon flux over carbon horizon;
+ PARAMETER woodYieldRota(location)                                          wood yield from forest rotation - MtC per Mha;
+ PARAMETER woodYieldLUC(land_cover_before, land_cover_after, location)      wood yield from land cover change;
+ PARAMETER forestRotationPeriod(location)           timber forest rotation period - years;
+ PARAMETER conversionCost(land_cover, land_cover)   cost of converting from one land cover to another;
+
+ SCALAR meatEfficency                        efficiency of converting feed and pasture into animal products;
+ SCALAR fertiliserUnitCost                   fert cost at max fert rate;
+ SCALAR otherIParam                          yield response to other intensity;
+ SCALAR otherICost                           cost of other intensity;
+ SCALAR unhandledCropRate                    rate of fruit veg and other crops not modelled;
+ SCALAR setAsideRate                         rate of set aside and failed crop;
+ SCALAR domesticPriceMarkup                  factor price increased from cost of production;
+ SCALAR maxLandExpansionRate                 max rate of country land expansion;
+
+ SCALAR woodDemand          Mt C-eq;
+ SCALAR woodExportPrice     price of wood export - $1000 per tC-eq;
+ SCALAR woodImportPrice     price of wood import - $1000 per tC-eq;
+ SCALAR woodMaxNetImport;
+ SCALAR woodMinNetImport;
+ SCALAR forestEstablishmentCost cast $1000 per hectare;
+ SCALAR vegClearingCostRate     cost of clearing vegetation $1000 per tC;
+ 
+ SCALAR carbonPrice         price of carbon - $1000 per tonne;
+
+*$gdxin "C:\Users\Bart\Documents\PhD\GAMS testing area\_gams_java_gdb1.gdx"
+$gdxin %gdxincname%
+$load location, suitableLandArea, demand
+$load previousCropArea, previousFertIntensity, previousIrrigIntensity, previousOtherIntensity, previousRuminantFeed, previousMonogastricFeed, previousImportAmount, previousExportAmount
+$load yieldNone, yieldFertOnly, yieldIrrigOnly, yieldBoth, yieldShock
+$load fertParam, irrigParam, otherIParam, exportPrices, importPrices, maxNetImport, minNetImport, unhandledCropRate, setAsideRate, maxLandExpansionRate, subsidyRate
+$load meatEfficency, otherICost, irrigCost, irrigMaxRate, irrigConstraint, fertiliserUnitCost, domesticPriceMarkup, minDemandPerCereal, minDemandPerOilcrop, seedAndWasteRate
+$load previousLandCoverArea, carbonFluxRateLUC, carbonFluxRateNEE, woodYieldRota, woodYieldLUC, forestRotationPeriod, conversionCost
+$load woodDemand, woodExportPrice, woodImportPrice, woodMaxNetImport, woodMinNetImport, forestEstablishmentCost, vegClearingCostRate, carbonPrice
+$gdxin
+
+ SCALAR delta "use to smooth power function see 7.5 www.gams.com dd docs solversconopt.pdf" / 0.00000000001 /;
+
+ demand(cereal_crop) = demand('cereals') * minDemandPerCereal(cereal_crop);
+ demand(oilpulse_crop) = demand('oilcropspulses') * minDemandPerOilcrop(oilpulse_crop);
+
+ previousCropArea(crop_less_pasture, location) = previousCropArea(crop_less_pasture, location) * (1.0 - unhandledCropRate);
+
+ PARAMETER cropDM(crop)  kg DM per kg of feed the conversion from feed to meat is done in the R animal product index.  Pasture is in DM terms already
+          /   wheat         0.87
+              maize         0.86
+              rice          0.89
+              oilcrops      0.96
+              pulses        0.31
+              starchyRoots  0.25
+              fruitveg      0.1
+              sugar         1
+              pasture       1    / ;
+
+ PARAMETER prodCost(crop)  cost per ha before intensity values not including fertiliser or irrigation i.e. seed spray and other.  In 1000 $ per ha
+          /   wheat             0.32
+              maize             0.31
+              rice              0.36
+              oilcrops          0.2
+              pulses            0.31
+              starchyRoots      3.14
+              fruitveg          4.0
+              sugar             3.0
+              energyCrops       0.34 / ;
+
+ PARAMETER baseCost(crop);
+ PARAMETER otherIntCost(crop);
+ baseCost(crop) = prodCost(crop)*0.3;
+ otherIntCost(crop) = baseCost(crop)*0.7 + otherICost;
+ baseCost('pasture') = 0.04;
+ otherIntCost('pasture') = 0.8 + otherICost;
+ 
+* maxNetImport(import_crop) = min(maxNetImport(import_crop), demand(import_crop));
+* minNetImport(import_crop) = min(minNetImport(import_crop), demand(import_crop));
+
+ VARIABLES
+       cropArea(crop, location)           total area for crops - Mha
+       fertI(crop, location)              fertilizer intensity for each crop - factor between 0 and 1
+       irrigI(crop, location)             irrigation intensity for each crop - factor between 0 and 1
+       otherIntensity(crop, location)
+       ruminantFeed(crop)                 amount of feed for ruminant animals - Mt
+       monogastricFeed(crop)              amount of feed for monogatric animals - Mt
+       importAmount(all_types)            imports of crops and meat - Mt
+       exportAmount(all_types)            exports of crops and meat - Mt
+       yield(crop, location)              yield per area for each crop - t per ha
+       unitCost(crop, location)           cost per area for each crop - cost
+       totalFeedDM                        total feed dry matter
+       supply(all_types)                  crop supply
+       landCoverArea(land_cover, location)  land cover area in Mha
+       landCoverChange(land_cover_before, land_cover_after, location) land cover change in Mha
+       totalConversionCost(location)      land cover conversion cost - $1000 per ha
+       woodExported                       total wood sold - Mt C-eq
+       woodImported
+       woodSupplyRota
+       woodSupplyLUC
+       woodProdCost
+       vegClearningCost
+       carbonFlux(location)               total carbon flux  - Mt C
+*       A                                  "artificial variable for debugging https://www.gams.com/blog/2017/07/misbehaving-model-infeasible/"
+       total_cost                         total cost of domestic supply including net imports;
+
+ POSITIVE VARIABLE cropArea, fertI, irrigI, otherIntensity, ruminantFeed, monogastricFeed, importAmount, exportAmount,
+                   agriLandExpansion, cropIncrease, cropDecrease, pastureDecrease, pastureIncrease, totalFeedDM,
+                   landCoverArea, landCoverChange, totalConversionCost, woodExported, woodImported, woodSupplyRota,
+                   woodSupplyLUC, woodProdCost, vegClearningCost;
+
+
+* POSITIVE VARIABLE A;
+
+ EQUATIONS
+       UNIT_COST_EQ(crop, location)                     cost per area - $1000 per ha or $billion per Mha
+       YIELD_EQ(crop, location)                         yield given chosen intensity - tonnes per hectare
+       MAX_FERT_INTENSITY_CONSTRAINT(crop, location)    constraint on maximum fertilizer intensity
+       MAX_IRRIG_INTENSITY_CONSTRAINT(crop, location)   constraint on maximum irrigation intensity
+       MAX_OTHER_INTENSITY_CONSTRAINT(crop, location)
+       IRRIGATION_CONSTRAINT(location)                  constraint on water usage
+       
+       SUPPLY_CALC(crop)
+       RUMINANTS_SUPPLY_CALC
+       MONOGASTRICS_SUPPLY_CALC
+       TOTAL_NON_PASTURE_FEED_DM_CALC                   calc total feed dry matter not including pasture
+       FEED_MIX_CONSTRAINT(feed_crop_less_pasture)      limit amount of feed for each feed crop
+       SUPPLY_CONSTRAINT(import_crop)
+
+       MAX_NET_IMPORT_CONSTRAINT(import_crop)           constraint on max net imports
+       MIN_NET_IMPORT_CONSTRAINT(import_crop)           constraint on min net imports
+       PASTURE_IMPORT_CONSTRAINT                        constraint to not import pasture
+       PASTURE_EXPORT_CONSTRAINT
+
+
+       SETASIDE_AREA_CALC(location)
+       CROPLAND_LAND_COVER_CALC(location)               cropland area equals sum of all crop areas
+       PASTURE_LAND_COVER_CALC(location)                pasture area (land cover) equals pasture area (land use)
+       LAND_COVER_CHANGE_CALC(land_cover, location)     "calc land cover change. landCoverChange(A, A, location) defined as unchanged land cover"
+       LAND_COVER_CHANGE_CONSTRAINT(land_cover, location) conservation of land area
+       CONVERSION_COST(location)                          cost of land cover conversion
+
+       WOOD_SUPPLY_ROTA_CALC
+       WOOD_SUPPLY_LUC_CALC
+       WOOD_SUPPLY_CONSTRAINT
+       WOOD_MIN_TRADE_CONSTRAINT                          constraint on minimum wood export
+       WOOD_MAX_TRADE_CONSTRAINT
+       WOOD_PROD_COST_CALC
+       VEG_CLEARING_COST_CALC
+ 
+       CARBON_FLUX_CALC(location)                         calc carbon flux      
+
+       COST_CALC         Net Present Value function;
+
+**************** Crop cost **************************
+
+ UNIT_COST_EQ(crop, location) .. unitCost(crop, location) =E=  (     baseCost(crop) +
+                                                                     fertiliserUnitCost * fertI(crop, location) +
+                                                                     irrigCost(location) * irrigMaxRate(crop, location) * irrigI(crop, location) +
+                                                                     otherIntCost(crop) * otherIntensity(crop, location)
+                                                                     );
+                                                                  
+**************** Crop yields *******************
+
+ YIELD_EQ(crop, location) .. yield(crop, location) =E= (
+               yieldNone(crop, location) +
+               (yieldFertOnly(crop, location) - yieldNone(crop, location)) * (1 - exp(-fertParam(crop, location)*fertI(crop, location))) +
+               (yieldIrrigOnly(crop, location) - yieldNone(crop, location)) * (1 - exp(-irrigParam(crop, location)*irrigI(crop, location))) +
+               (yieldBoth(crop, location) + yieldNone(crop, location) - yieldFertOnly(crop, location) - yieldIrrigOnly(crop, location)) *
+                                      (1 - exp(-fertParam(crop, location)*fertI(crop, location))) * (1 - exp(-irrigParam(crop, location)*irrigI(crop, location)))
+            ) * (1 - exp(-otherIntensity(crop, location)*otherIParam));
+            
+ MAX_FERT_INTENSITY_CONSTRAINT(crop, location) .. fertI(crop, location) =L= 1;
+ MAX_IRRIG_INTENSITY_CONSTRAINT(crop, location) .. irrigI(crop, location) =L= 1;
+ MAX_OTHER_INTENSITY_CONSTRAINT(crop, location) .. otherIntensity(crop, location) =L= 1;
+ 
+ IRRIGATION_CONSTRAINT(location) .. irrigConstraint(location) * suitableLandArea(location) * (1.0 - unhandledCropRate) =G= sum(crop, irrigMaxRate(crop, location) * irrigI(crop, location) * cropArea(crop, location));
+
+*************** Commodity supply *************************
+
+ SUPPLY_CALC(crop) .. supply(crop) =E= sum(location, cropArea(crop, location) * yield(crop, location)) * (1 - seedAndWasteRate(crop)) - ruminantFeed(crop) - monogastricFeed(crop);
+ 
+ RUMINANTS_SUPPLY_CALC .. supply('ruminants') =E= meatEfficency * sum(feed_crop, ruminantFeed(feed_crop) * cropDM(feed_crop)) * (1 - seedAndWasteRate('ruminants'));
+ 
+ MONOGASTRICS_SUPPLY_CALC .. supply('monogastrics') =E= meatEfficency * sum(feed_crop_less_pasture, monogastricFeed(feed_crop_less_pasture) * cropDM(feed_crop_less_pasture)) * (1 - seedAndWasteRate('monogastrics'));
+
+ TOTAL_NON_PASTURE_FEED_DM_CALC .. totalFeedDM =E= sum(feed_crop_less_pasture, (ruminantFeed(feed_crop_less_pasture) + monogastricFeed(feed_crop_less_pasture)) * cropDM(feed_crop_less_pasture));
+ FEED_MIX_CONSTRAINT(feed_crop_less_pasture) .. (ruminantFeed(feed_crop_less_pasture) + monogastricFeed(feed_crop_less_pasture)) * cropDM(feed_crop_less_pasture) =L= totalFeedDM * 0.7;
+
+ SUPPLY_CONSTRAINT(import_crop) .. supply(import_crop) =E= demand(import_crop) + exportAmount(import_crop) - importAmount(import_crop);
+
+*************** Exports *******************************
+
+ MAX_NET_IMPORT_CONSTRAINT(import_crop) .. importAmount(import_crop) - exportAmount(import_crop) =L= maxNetImport(import_crop);
+ MIN_NET_IMPORT_CONSTRAINT(import_crop) .. importAmount(import_crop) - exportAmount(import_crop) =G= minNetImport(import_crop);
+ PASTURE_IMPORT_CONSTRAINT .. importAmount('pasture') =E= 0;
+ PASTURE_EXPORT_CONSTRAINT ..  exportAmount('pasture') =E= 0;
+
+************** Land Cover *****************************
+
+ SETASIDE_AREA_CALC(location) .. cropArea('setaside', location) =E= sum(crop_less_pasture, cropArea(crop_less_pasture, location)) * setAsideRate;
+ 
+ CROPLAND_LAND_COVER_CALC(location) .. landCoverArea('cropland', location) =E= sum(crop_less_pasture, cropArea(crop_less_pasture, location)) / (1.0 - unhandledCropRate);
+ 
+ PASTURE_LAND_COVER_CALC(location) .. landCoverArea('pasture', location) =E= cropArea('pasture', location);
+
+ LAND_COVER_CHANGE_CALC(land_cover, location) .. landCoverArea(land_cover, location) =E= previousLandCoverArea(land_cover, location) +
+                                                                                         sum(land_cover_before, landCoverChange(land_cover_before, land_cover, location)) -
+                                                                                         sum(land_cover_after, landCoverChange(land_cover, land_cover_after, location));
+
+
+ LAND_COVER_CHANGE_CONSTRAINT(land_cover, location) .. sum(land_cover_after, landCoverChange(land_cover, land_cover_after, location)) =E= previousLandCoverArea(land_cover, location);
+
+ CONVERSION_COST(location) .. totalConversionCost(location) =E= sum((land_cover_before, land_cover_after), landCoverChange(land_cover_before, land_cover_after, location) * conversionCost(land_cover_before, land_cover_after));
+
+************* Forestry ***********************************
+
+ WOOD_SUPPLY_ROTA_CALC .. woodSupplyRota =E= sum(location, landCoverArea('timberForest', location) * woodYieldRota(location) / forestRotationPeriod(location));
+ 
+ WOOD_SUPPLY_LUC_CALC .. woodSupplyLUC =E= sum((land_cover_before, land_cover_after, location), landCoverChange(land_cover_before, land_cover_after, location) * woodYieldLUC(land_cover_before, land_cover_after, location));
+ 
+ WOOD_SUPPLY_CONSTRAINT .. woodSupplyRota =E= woodDemand + woodExported - woodImported;
+
+ WOOD_MIN_TRADE_CONSTRAINT .. woodImported - woodExported =L= woodMaxNetImport;
+
+ WOOD_MAX_TRADE_CONSTRAINT .. woodImported - woodExported =G= woodMinNetImport;
+ 
+ WOOD_PROD_COST_CALC .. woodProdCost =E= sum(location, landCoverArea('timberForest', location) * forestEstablishmentCost / forestRotationPeriod(location));
+ 
+ VEG_CLEARING_COST_CALC .. vegClearningCost =E= woodSupplyLUC * vegClearingCostRate;
+ 
+*********** Carbon fluxes ***********************************
+
+ CARBON_FLUX_CALC(location) .. carbonFlux(location) =E= sum((land_cover_before, land_cover_after),
+                landCoverChange(land_cover_before, land_cover_after, location) * carbonFluxRateLUC(land_cover_before, land_cover_after, location)) +
+                sum((land_cover_before, land_cover_after)$[not sameAs(land_cover_before, land_cover_after)],
+                landCoverChange(land_cover_before, land_cover_after, location) * carbonFluxRateNEE(land_cover_after, location));
+
+************ Total cost ******************************
+
+ COST_CALC .. total_cost =E= sum((crop, location), cropArea(crop, location) * unitCost(crop, location) * (1-subsidyRate(crop))) * domesticPriceMarkup +
+                       sum(location, totalConversionCost(location)) +
+                       sum(import_crop, importPrices(import_crop) * importAmount(import_crop)) +
+                       woodImported * woodImportPrice -
+                       sum(import_crop, exportPrices(import_crop) * exportAmount(import_crop)) -
+                       woodExported * woodExportPrice +
+                       woodProdCost +
+                       vegClearningCost +
+                       sum(location, carbonFlux(location)) * carbonPrice;
+                       
+                  
+
+
+ MODEL LAND_USE /ALL/ ;
+ fertI.L(crop, location) = previousFertIntensity(crop, location);
+ irrigI.L(crop, location) = previousIrrigIntensity(crop, location);
+ otherIntensity.L(crop, location) = previousOtherIntensity(crop, location);
+ cropArea.L(crop, location) = previousCropArea(crop, location);
+ landCoverArea.L(land_cover, location) = previousLandCoverArea(land_cover, location);
+ landCoverChange.L(land_cover, land_cover, location) = previousLandCoverArea(land_cover, location);
+ ruminantFeed.L(feed_crop) = previousRuminantFeed(feed_crop);
+ monogastricFeed.L(feed_crop) = previousMonogastricFeed(feed_crop);
+ importAmount.L(all_types) = previousImportAmount(all_types);
+ exportAmount.L(all_types) = previousExportAmount(all_types);
+
+* LAND_USE.OptFile = 1;
+
+* display landCoverChange.L
+
+ SOLVE LAND_USE USING NLP MINIMIZING total_cost;
+
+ display previousCropArea, irrigMaxRate, otherIntensity.L, fertI.L, irrigI.L, cropArea.L;
+ display supply.l, demand, ruminantFeed.l, monogastricFeed.l, exportAmount.l;
+
+* Calculate summary information used in Java process
+ parameter totalProd(all_types);
+ parameter totalProdCost(all_types);
+ parameter totalArea(crop);
+ parameter totalCropland(location);
+ parameter netImportAmount(all_types);
+ parameter netImportCost(all_types);
+ parameter feedCostRate(feed_crop);
+ parameter productionShock(all_types);
+ scalar netCarbonFlux;
+ scalar netWoodImport;
+ scalar totalWoodHarvest;
+
+* Production quantities based on smaller area (before unhandledCropArea adjustment applied)
+ totalProd(crop) = sum(location, cropArea.l(crop, location) * yield.l(crop, location));
+ productionShock(crop) = sum(location, cropArea.l(crop, location) * yield.l(crop, location) * yieldShock(crop, location));
+ totalProd('ruminants') = meatEfficency*(sum(feed_crop, ruminantFeed.l(feed_crop) * cropDM(feed_crop)));
+ totalProd('monogastrics') = meatEfficency*(sum(feed_crop, monogastricFeed.l(feed_crop) * cropDM(feed_crop)));
+
+* Cost based on adjusted area
+ cropArea.l(crop_less_pasture, location) = cropArea.l(crop_less_pasture, location) / (1.0 - unhandledCropRate);
+ totalProdCost(crop) = sum(location, unitCost.l(crop, location) * cropArea.l(crop, location));
+ totalCropland(location) = sum(crop_less_pasture, cropArea.l(crop_less_pasture, location));
+ totalArea(crop) = sum(location, cropArea.l(crop, location));
+
+ feedCostRate(feed_crop)$[totalProd(feed_crop) <> 0] = totalProdCost(feed_crop) / totalProd(feed_crop);
+ totalProdCost('ruminants') = sum(feed_crop, ruminantFeed.l(feed_crop) * feedCostRate(feed_crop));
+ totalProdCost('monogastrics') = sum(feed_crop, monogastricFeed.l(feed_crop) * feedCostRate(feed_crop));
+
+ netImportAmount(import_crop) = importAmount.l(import_crop) - exportAmount.l(import_crop);
+ netImportCost(import_crop) = importAmount.l(import_crop) * importPrices(import_crop) - exportAmount.l(import_crop) * exportPrices(import_crop);
+
+
+ netCarbonFlux = SUM(location, carbonFlux.L(location));
+* including wood from LUC as export
+ netWoodImport = woodImported.L - woodExported.L;
+* netWoodImport = 0;
+ totalWoodHarvest = woodSupplyRota.L + woodSupplyLUC.L;
+
+ Scalar totalCostsLessLU;
+
+ totalCostsLessLU = sum(crop,totalProdCost(crop))+ sum(import_crop,netImportCost(import_crop));
+
+
+ Scalar ms 'model status', ss 'solve status';
+ ms=LAND_USE.modelstat;
+ ss=LAND_USE.solvestat;
diff --git a/src/ac/ed/lurg/InternationalMarket.java b/src/ac/ed/lurg/InternationalMarket.java
index 8934b0359eb93aeb50fcdebb6ea8c71e0c63829c..86350e1f3732cd35df42a619771cbfeccfb6e1df 100644
--- a/src/ac/ed/lurg/InternationalMarket.java
+++ b/src/ac/ed/lurg/InternationalMarket.java
@@ -1,293 +1,295 @@
-package ac.ed.lurg;
-
-import java.io.BufferedWriter;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-import ac.ed.lurg.country.AbstractCountryAgent;
-import ac.ed.lurg.country.GlobalPrice;
-import ac.ed.lurg.country.StockReader;
-import ac.ed.lurg.landuse.CropUsageData;
-import ac.ed.lurg.landuse.WoodUsageData;
-import ac.ed.lurg.shock.PriceShockManager;
-import ac.ed.lurg.types.CropToDoubleMap;
-import ac.ed.lurg.types.CropType;
-import ac.ed.lurg.types.WoodType;
-import ac.ed.lurg.utils.LogWriter;
-
-public class InternationalMarket {
-
-	private Map<CropType, GlobalPrice> worldPrices;
-	private GlobalPrice carbonPrice;
-	private GlobalPrice woodPrice;
-	private PriceShockManager priceShockManager;
-	
-	@SuppressWarnings("unchecked")
-	public InternationalMarket() {
-		if(ModelConfig.IS_CALIBRATION_RUN) {
-			worldPrices = new HashMap<CropType, GlobalPrice>();
-
-
-			Map<CropType, Double> stockLevel = getInitialStockLevels();
-
-			for (CropType crop : CropType.getImportedTypes()) {
-				Double initialStock = stockLevel.get(crop);
-				worldPrices.put(crop, GlobalPrice.createInitial(crop.getInitialPrice(), initialStock));
-			}
-			
-			carbonPrice = GlobalPrice.createInitial(ModelConfig.INIT_CARBON_PRICE, 0);
-			woodPrice = GlobalPrice.createInitial(ModelConfig.INIT_WOOD_PRICE, ModelConfig.INIT_WOOD_STOCK);
-
-		}
-		else {
-			List<Object> deserializedPrices = deserializeGlobalPrice();
-			worldPrices = (Map<CropType, GlobalPrice>) deserializedPrices.get(0);
-			carbonPrice = (GlobalPrice) deserializedPrices.get(1);
-			woodPrice = (GlobalPrice) deserializedPrices.get(2);
-
-		}
-		priceShockManager = new PriceShockManager();
-	}
-
-	public  Map<CropType, GlobalPrice> getWorldPrices() {
-		return worldPrices;
-	}
-	
-	public GlobalPrice getCarbonPrice() {
-		if (ModelConfig.IS_CARBON_ON) {
-			return carbonPrice;
-		} else {
-			return carbonPrice.createPriceAdjustedByFactor(0); // set price to 0 but keep export/import amounts for reference
-		}
-	}
-	
-	public GlobalPrice getWoodPrice() {
-		if (ModelConfig.IS_FORESTRY_ON) {
-			return woodPrice;
-		} else {
-			return woodPrice.createPriceAdjustedByFactor(0); 
-		}
-	}
-
-	private Map<CropType, Double> getInitialStockLevels() {
-		Map<CropType, Double> initialStockLevels = new StockReader().read();
-		return initialStockLevels;
-	}
-
-	void determineInternationalTrade(Collection<AbstractCountryAgent> countryAgents, double carbonDemand, Timestep timestep) {
-		CropToDoubleMap totalImportCommodities = new CropToDoubleMap();
-		CropToDoubleMap totalExportCommodities = new CropToDoubleMap();
-		CropToDoubleMap totalProduction = new CropToDoubleMap();
-		for (AbstractCountryAgent ca : countryAgents) {
-
-			// Get values for world input costs
-			Map<CropType, CropUsageData> cropUsage = ca.getCropUsageData();
-
-			for (Entry<CropType, CropUsageData> entry : cropUsage.entrySet()) {
-				CropType c = entry.getKey();
-
-				double countryNetImports = entry.getValue().getShockedNetImports();
-				totalProduction.incrementValue(c, (entry.getValue().getProductionExpected()-entry.getValue().getProductionShock()));
-
-				if (countryNetImports > 0)
-					totalImportCommodities.incrementValue(c, countryNetImports);
-				else
-					totalExportCommodities.incrementValue(c, -countryNetImports);
-			}
-		}
-
-		// Look at trade balance and adjust appropriately
-		for (CropType crop : CropType.getImportedTypes()) {
-
-			GlobalPrice prevPrice = worldPrices.get(crop);
-			double imports = totalImportCommodities.containsKey(crop) ? totalImportCommodities.get(crop) : 0.0;
-			double exportsBeforeTransportLosses = totalExportCommodities.containsKey(crop) ? totalExportCommodities.get(crop) : 0.0;
-			LogWriter.println(timestep.getYear() + " Updating " + crop.getGamsName() + " prices");
-			GlobalPrice adjustedPrice = prevPrice.createWithUpdatedMarketPrices(imports, exportsBeforeTransportLosses, timestep, totalProduction.get(crop), true);
-			LogWriter.println( String.format("Price for %s updated from %s to %s \n", crop.getGamsName(), prevPrice, adjustedPrice));
-			if (adjustedPrice.getStockLevel() < 0)
-				LogWriter.println("Global stocks are below zero" + crop.getGamsName() + ", " + timestep.getYear());
-
-			worldPrices.put(crop, adjustedPrice);
-		}
-		
-		// Update carbon price
-		double totalCarbonSequestered = 0;
-		double totalCarbonEmitted = 0;
-		for (AbstractCountryAgent ca : countryAgents) {
-			double netCarbonFlux = ca.getNetCarbonFlux();
-			if (netCarbonFlux >= 0)
-				totalCarbonEmitted += netCarbonFlux;
-			else
-				totalCarbonSequestered -= netCarbonFlux;
-		}
-		
-		totalCarbonSequestered = Math.max(totalCarbonSequestered, 0.0000001); // avoid division by 0
-		GlobalPrice prevCPrice = carbonPrice;
-		LogWriter.println(timestep.getYear() + " Updating carbon price");
-		GlobalPrice adjustedCPrice = prevCPrice.createWithUpdatedMarketPrices(carbonDemand, totalCarbonSequestered, timestep, totalCarbonSequestered, false);
-		LogWriter.println( String.format("Price for carbon updated from %s to %s \n", prevCPrice, adjustedCPrice));
-		if (adjustedCPrice.getStockLevel() < 0)
-			LogWriter.println("Global stocks are below zero carbon, " + timestep.getYear());
-		carbonPrice = adjustedCPrice;
-		
-		// Update timber price
-		double totalWoodImport = 0;
-		double totalWoodExport = 0;
-		double totalWoodProduction = 0;
-		
-		for (AbstractCountryAgent ca : countryAgents) {
-			Map<WoodType, WoodUsageData> woodUsageMap = ca.getWoodUsageData();
-			for (WoodUsageData woodUsage : woodUsageMap.values()) {
-				totalWoodProduction += woodUsage.getHarvest();
-				double netImport = woodUsage.getNetImport();
-				if (netImport >= 0) {
-					totalWoodImport += netImport;
-				} else {
-					totalWoodExport += -netImport;
-				}
-			}
-		}
-		totalWoodProduction = Math.max(totalWoodProduction, 0.0000001); // avoid division by 0
-		GlobalPrice prevTPrice = woodPrice;
-		LogWriter.println(timestep.getYear() + " Updating wood price");
-		GlobalPrice adjustedTPrice = prevTPrice.createWithUpdatedMarketPrices(totalWoodImport, totalWoodExport, timestep, totalWoodProduction, true);
-		LogWriter.println( String.format("Price for wood updated from %s to %s \n", prevTPrice, adjustedTPrice));
-		if (adjustedTPrice.getStockLevel() < 0)
-			LogWriter.println("Global stocks are below zero wood, " + timestep.getYear());
-		woodPrice = adjustedTPrice;
-
-	}
-
-	void writeGlobalMarketFile(Timestep timestep, BufferedWriter outputFile) throws IOException {
-		for (CropType crop : CropType.getImportedTypes()) {
-			StringBuffer sbData = new StringBuffer();
-			GlobalPrice priceQuantity = worldPrices.get(crop);
-			sbData.append(String.format("%d,%s", timestep.getYear(), crop.getGamsName()));
-			sbData.append(String.format(",%.1f,%.1f", priceQuantity.getImportAmount(), priceQuantity.getExportsAfterTransportLosses()));
-			sbData.append(String.format(",%.8f,%.3f", priceQuantity.getExportPrice(), priceQuantity.getStockLevel()));
-
-			outputFile.write(sbData.toString());
-			outputFile.newLine();
-		}
-		// Carbon price
-		{
-			StringBuffer sbData = new StringBuffer();
-			sbData.append(String.format("%d,%s", timestep.getYear(), "carbon"));
-			sbData.append(String.format(",%.1f,%.1f", carbonPrice.getImportAmount(), carbonPrice.getExportsAfterTransportLosses()));
-			sbData.append(String.format(",%.8f,%.3f", carbonPrice.getExportPrice(), carbonPrice.getStockLevel()));
-	
-			outputFile.write(sbData.toString());
-			outputFile.newLine();
-		}
-		// Timber price
-		{
-			StringBuffer sbData = new StringBuffer();
-			sbData.append(String.format("%d,%s", timestep.getYear(), "timber"));
-			sbData.append(String.format(",%.1f,%.1f", woodPrice.getImportAmount(), woodPrice.getExportsAfterTransportLosses()));
-			sbData.append(String.format(",%.8f,%.3f", woodPrice.getExportPrice(), woodPrice.getStockLevel()));
-	
-			outputFile.write(sbData.toString());
-			outputFile.newLine();
-		}
-	}
-
-	public void serializeGlobalPrices() {
-		List<Object> pricesToSerialize = new ArrayList<Object>();
-		pricesToSerialize.add(worldPrices);
-		pricesToSerialize.add(carbonPrice);
-		pricesToSerialize.add(woodPrice);
-
-		try {
-			String fileStr = ModelConfig.IS_CALIBRATION_RUN ? ModelConfig.SERIALIZED_INTERNATIONAL_MARKET_FILE : ModelConfig.CHECKPOINT_INTERNATIONAL_MARKET_FILE;
-			LogWriter.println("Starting serializing GlobalPrice to " + fileStr);
-			FileOutputStream fileOut = new FileOutputStream(fileStr);
-			ObjectOutputStream out = new ObjectOutputStream(fileOut);
-			out.writeObject(pricesToSerialize);
-			out.close();
-			fileOut.close();
-			LogWriter.println("Serialized international market data is saved");
-		} catch (IOException i) {
-			i.printStackTrace();
-		}
-	}
-
-	@SuppressWarnings("unchecked")
-	private List<Object> deserializeGlobalPrice() {
-		try {
-			List<Object> initGlobalPrices;
-			FileInputStream fileIn = new FileInputStream(ModelConfig.SERIALIZED_INTERNATIONAL_MARKET_FILE);
-			ObjectInputStream in = new ObjectInputStream(fileIn);
-			initGlobalPrices = (List<Object>) in.readObject();
-			in.close();
-			fileIn.close();
-			LogWriter.println("Deserialized " + ModelConfig.SERIALIZED_INTERNATIONAL_MARKET_FILE);
-			return initGlobalPrices;
-		} catch (IOException i) {
-			LogWriter.printlnError("Problem deserializing " + ModelConfig.SERIALIZED_INTERNATIONAL_MARKET_FILE);
-			LogWriter.print(i);
-			return null;
-		} catch (ClassNotFoundException c) {
-			LogWriter.printlnError("GlobalPrice class not found");
-			c.printStackTrace();
-			return null;
-		}
-	}
-
-	public void applyPriceShocks(Timestep timestep) {
-		Map<CropType, Double> shocks = priceShockManager.getShocks(timestep);
-		for (Map.Entry<CropType, Double> entry : shocks.entrySet()) {
-			CropType crop = entry.getKey();
-			GlobalPrice preShock = worldPrices.get(crop);
-			double factor = 1.0 + entry.getValue();
-			GlobalPrice adjustedPrice = preShock.createPriceAdjustedByFactor(factor);
-			LogWriter.println(String.format("applyPriceShocks: %s adjusting price by %.4f to export price %.2f", crop.getFaoName(), factor, adjustedPrice.getExportPrice()));
-			worldPrices.put(crop, adjustedPrice);
-		}
-	}
-
-	public boolean negativeStockLevelsExist() {
-		for (Map.Entry<CropType, GlobalPrice> entry : worldPrices.entrySet()) {
-			double stocklevel = entry.getValue().getStockLevel();
-			if (stocklevel < 0) {
-				LogWriter.println(String.format("negativeStockLevelsExist: %s has negative stock %.3f", entry.getKey().getFaoName(), stocklevel));
-				return true;
-			}
-		}
-		LogWriter.println(String.format("No negative stocks found"));
-		return false;
-	}
-
-	/*	public double findMaxPriceDiff(Map<CropType, Double> previousExportPrices) {
-		if (previousExportPrices == null)
-			return Double.MAX_VALUE;
-
-		double maxSoFar = 0;
-		CropType crop = null;
-
-		for (Entry<CropType, GlobalPrice> entry : worldPrices.entrySet()) {
-			Double previousP = previousExportPrices.get(entry.getKey());
-
-			if (previousP == null)
-				return Double.MAX_VALUE;
-
-			double diffThisTime = Math.abs(previousP - entry.getValue().getExportPrice());
-			if (diffThisTime > maxSoFar) {
-				maxSoFar = diffThisTime;
-				crop = entry.getKey();
-			}
-		}
-
-		LogWriter.println("findMaxPriceDiff: found " + maxSoFar + " for " + crop);
-		return maxSoFar;
-	} */
-}
+package ac.ed.lurg;
+
+import java.io.BufferedWriter;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import ac.ed.lurg.country.AbstractCountryAgent;
+import ac.ed.lurg.country.GlobalPrice;
+import ac.ed.lurg.country.StockReader;
+import ac.ed.lurg.landuse.CropUsageData;
+import ac.ed.lurg.landuse.WoodUsageData;
+import ac.ed.lurg.shock.PriceShockManager;
+import ac.ed.lurg.types.CropToDoubleMap;
+import ac.ed.lurg.types.CropType;
+import ac.ed.lurg.types.WoodType;
+import ac.ed.lurg.utils.LogWriter;
+
+public class InternationalMarket {
+
+	private Map<CropType, GlobalPrice> worldPrices;
+	private GlobalPrice carbonPrice;
+	private GlobalPrice woodPrice;
+	private PriceShockManager priceShockManager;
+	
+	@SuppressWarnings("unchecked")
+	public InternationalMarket() {
+		if(ModelConfig.IS_CALIBRATION_RUN) {
+			worldPrices = new HashMap<CropType, GlobalPrice>();
+
+
+			Map<CropType, Double> stockLevel = getInitialStockLevels();
+
+			for (CropType crop : CropType.getImportedTypes()) {
+				Double initialStock = stockLevel.get(crop);
+				worldPrices.put(crop, GlobalPrice.createInitial(crop.getInitialPrice(), initialStock));
+			}
+			
+			carbonPrice = GlobalPrice.createInitial(ModelConfig.INIT_CARBON_PRICE, 0);
+			woodPrice = GlobalPrice.createInitial(ModelConfig.INIT_WOOD_PRICE, ModelConfig.INIT_WOOD_STOCK);
+
+		}
+		else {
+			List<Object> deserializedPrices = deserializeGlobalPrice();
+			worldPrices = (Map<CropType, GlobalPrice>) deserializedPrices.get(0);
+			//carbonPrice = (GlobalPrice) deserializedPrices.get(1);
+			carbonPrice = GlobalPrice.createInitial(ModelConfig.INIT_CARBON_PRICE, 0);
+			woodPrice = (GlobalPrice) deserializedPrices.get(2);
+
+		}
+		priceShockManager = new PriceShockManager();
+	}
+
+	public  Map<CropType, GlobalPrice> getWorldPrices() {
+		return worldPrices;
+	}
+	
+	public GlobalPrice getCarbonPrice() {
+		if (ModelConfig.IS_CARBON_ON) {
+			return carbonPrice;
+		} else {
+			return carbonPrice.createPriceAdjustedByFactor(0); // set price to 0 but keep export/import amounts for reference
+		}
+	}
+	
+	public GlobalPrice getWoodPrice() {
+		if (ModelConfig.IS_FORESTRY_ON) {
+			return woodPrice;
+		} else {
+			return woodPrice.createPriceAdjustedByFactor(0); 
+		}
+	}
+
+	private Map<CropType, Double> getInitialStockLevels() {
+		Map<CropType, Double> initialStockLevels = new StockReader().read();
+		return initialStockLevels;
+	}
+
+	void determineInternationalTrade(Collection<AbstractCountryAgent> countryAgents, double carbonDemand, Timestep timestep) {
+		CropToDoubleMap totalImportCommodities = new CropToDoubleMap();
+		CropToDoubleMap totalExportCommodities = new CropToDoubleMap();
+		CropToDoubleMap totalProduction = new CropToDoubleMap();
+		for (AbstractCountryAgent ca : countryAgents) {
+
+			// Get values for world input costs
+			Map<CropType, CropUsageData> cropUsage = ca.getCropUsageData();
+
+			for (Entry<CropType, CropUsageData> entry : cropUsage.entrySet()) {
+				CropType c = entry.getKey();
+
+				double countryNetImports = entry.getValue().getShockedNetImports();
+				totalProduction.incrementValue(c, (entry.getValue().getProductionExpected()-entry.getValue().getProductionShock()));
+
+				if (countryNetImports > 0)
+					totalImportCommodities.incrementValue(c, countryNetImports);
+				else
+					totalExportCommodities.incrementValue(c, -countryNetImports);
+			}
+		}
+
+		// Look at trade balance and adjust appropriately
+		for (CropType crop : CropType.getImportedTypes()) {
+
+			GlobalPrice prevPrice = worldPrices.get(crop);
+			double imports = totalImportCommodities.containsKey(crop) ? totalImportCommodities.get(crop) : 0.0;
+			double exportsBeforeTransportLosses = totalExportCommodities.containsKey(crop) ? totalExportCommodities.get(crop) : 0.0;
+			LogWriter.println(timestep.getYear() + " Updating " + crop.getGamsName() + " prices");
+			GlobalPrice adjustedPrice = prevPrice.createWithUpdatedMarketPrices(imports, exportsBeforeTransportLosses, timestep, totalProduction.get(crop), true);
+			LogWriter.println( String.format("Price for %s updated from %s to %s \n", crop.getGamsName(), prevPrice, adjustedPrice));
+			if (adjustedPrice.getStockLevel() < 0)
+				LogWriter.println("Global stocks are below zero" + crop.getGamsName() + ", " + timestep.getYear());
+
+			worldPrices.put(crop, adjustedPrice);
+		}
+		
+		// Update carbon price
+		double totalCarbonSequestered = 0;
+		double totalCarbonEmitted = 0;
+		for (AbstractCountryAgent ca : countryAgents) {
+			double netCarbonFlux = ca.getNetCarbonFlux();
+			if (netCarbonFlux >= 0)
+				totalCarbonEmitted += netCarbonFlux;
+			else
+				totalCarbonSequestered -= netCarbonFlux;
+		}
+		
+		totalCarbonSequestered = Math.max(totalCarbonSequestered, 0.0000001); // avoid division by 0
+		GlobalPrice prevCPrice = carbonPrice;
+		LogWriter.println(timestep.getYear() + " Updating carbon price");
+		GlobalPrice adjustedCPrice = prevCPrice.createWithUpdatedMarketPrices(carbonDemand, totalCarbonSequestered, timestep, totalCarbonSequestered, false);
+		LogWriter.println( String.format("Price for carbon updated from %s to %s \n", prevCPrice, adjustedCPrice));
+		if (adjustedCPrice.getStockLevel() < 0)
+			LogWriter.println("Global stocks are below zero carbon, " + timestep.getYear());
+		carbonPrice = adjustedCPrice;
+		
+		// Update timber price
+		double totalWoodImport = 0;
+		double totalWoodExport = 0;
+		double totalWoodProduction = 0;
+		
+		for (AbstractCountryAgent ca : countryAgents) {
+			Map<WoodType, WoodUsageData> woodUsageMap = ca.getWoodUsageData();
+			for (WoodUsageData woodUsage : woodUsageMap.values()) {
+				totalWoodProduction += woodUsage.getHarvest();
+				double netImport = woodUsage.getNetImport();
+				double lucHarvest = woodUsage.getLucHarvest(); // assume wood from LUC exported
+				if (netImport >= 0) {
+					totalWoodImport += netImport;
+				} else {
+					totalWoodExport += -netImport + lucHarvest;
+				}
+			}
+		}
+		totalWoodProduction = Math.max(totalWoodProduction, 0.0000001); // avoid division by 0
+		GlobalPrice prevTPrice = woodPrice;
+		LogWriter.println(timestep.getYear() + " Updating wood price");
+		GlobalPrice adjustedTPrice = prevTPrice.createWithUpdatedMarketPrices(totalWoodImport, totalWoodExport, timestep, totalWoodProduction, true);
+		LogWriter.println( String.format("Price for wood updated from %s to %s \n", prevTPrice, adjustedTPrice));
+		if (adjustedTPrice.getStockLevel() < 0)
+			LogWriter.println("Global stocks are below zero wood, " + timestep.getYear());
+		woodPrice = adjustedTPrice;
+
+	}
+
+	void writeGlobalMarketFile(Timestep timestep, BufferedWriter outputFile) throws IOException {
+		for (CropType crop : CropType.getImportedTypes()) {
+			StringBuffer sbData = new StringBuffer();
+			GlobalPrice priceQuantity = worldPrices.get(crop);
+			sbData.append(String.format("%d,%s", timestep.getYear(), crop.getGamsName()));
+			sbData.append(String.format(",%.1f,%.1f", priceQuantity.getImportAmount(), priceQuantity.getExportsAfterTransportLosses()));
+			sbData.append(String.format(",%.8f,%.3f", priceQuantity.getExportPrice(), priceQuantity.getStockLevel()));
+
+			outputFile.write(sbData.toString());
+			outputFile.newLine();
+		}
+		// Carbon price
+		{
+			StringBuffer sbData = new StringBuffer();
+			sbData.append(String.format("%d,%s", timestep.getYear(), "carbon"));
+			sbData.append(String.format(",%.1f,%.1f", carbonPrice.getImportAmount(), carbonPrice.getExportsAfterTransportLosses()));
+			sbData.append(String.format(",%.8f,%.3f", carbonPrice.getExportPrice(), carbonPrice.getStockLevel()));
+	
+			outputFile.write(sbData.toString());
+			outputFile.newLine();
+		}
+		// Timber price
+		{
+			StringBuffer sbData = new StringBuffer();
+			sbData.append(String.format("%d,%s", timestep.getYear(), "timber"));
+			sbData.append(String.format(",%.1f,%.1f", woodPrice.getImportAmount(), woodPrice.getExportsAfterTransportLosses()));
+			sbData.append(String.format(",%.8f,%.3f", woodPrice.getExportPrice(), woodPrice.getStockLevel()));
+	
+			outputFile.write(sbData.toString());
+			outputFile.newLine();
+		}
+	}
+
+	public void serializeGlobalPrices() {
+		List<Object> pricesToSerialize = new ArrayList<Object>();
+		pricesToSerialize.add(worldPrices);
+		pricesToSerialize.add(carbonPrice);
+		pricesToSerialize.add(woodPrice);
+
+		try {
+			String fileStr = ModelConfig.IS_CALIBRATION_RUN ? ModelConfig.SERIALIZED_INTERNATIONAL_MARKET_FILE : ModelConfig.CHECKPOINT_INTERNATIONAL_MARKET_FILE;
+			LogWriter.println("Starting serializing GlobalPrice to " + fileStr);
+			FileOutputStream fileOut = new FileOutputStream(fileStr);
+			ObjectOutputStream out = new ObjectOutputStream(fileOut);
+			out.writeObject(pricesToSerialize);
+			out.close();
+			fileOut.close();
+			LogWriter.println("Serialized international market data is saved");
+		} catch (IOException i) {
+			i.printStackTrace();
+		}
+	}
+
+	@SuppressWarnings("unchecked")
+	private List<Object> deserializeGlobalPrice() {
+		try {
+			List<Object> initGlobalPrices;
+			FileInputStream fileIn = new FileInputStream(ModelConfig.SERIALIZED_INTERNATIONAL_MARKET_FILE);
+			ObjectInputStream in = new ObjectInputStream(fileIn);
+			initGlobalPrices = (List<Object>) in.readObject();
+			in.close();
+			fileIn.close();
+			LogWriter.println("Deserialized " + ModelConfig.SERIALIZED_INTERNATIONAL_MARKET_FILE);
+			return initGlobalPrices;
+		} catch (IOException i) {
+			LogWriter.printlnError("Problem deserializing " + ModelConfig.SERIALIZED_INTERNATIONAL_MARKET_FILE);
+			LogWriter.print(i);
+			return null;
+		} catch (ClassNotFoundException c) {
+			LogWriter.printlnError("GlobalPrice class not found");
+			c.printStackTrace();
+			return null;
+		}
+	}
+
+	public void applyPriceShocks(Timestep timestep) {
+		Map<CropType, Double> shocks = priceShockManager.getShocks(timestep);
+		for (Map.Entry<CropType, Double> entry : shocks.entrySet()) {
+			CropType crop = entry.getKey();
+			GlobalPrice preShock = worldPrices.get(crop);
+			double factor = 1.0 + entry.getValue();
+			GlobalPrice adjustedPrice = preShock.createPriceAdjustedByFactor(factor);
+			LogWriter.println(String.format("applyPriceShocks: %s adjusting price by %.4f to export price %.2f", crop.getFaoName(), factor, adjustedPrice.getExportPrice()));
+			worldPrices.put(crop, adjustedPrice);
+		}
+	}
+
+	public boolean negativeStockLevelsExist() {
+		for (Map.Entry<CropType, GlobalPrice> entry : worldPrices.entrySet()) {
+			double stocklevel = entry.getValue().getStockLevel();
+			if (stocklevel < 0) {
+				LogWriter.println(String.format("negativeStockLevelsExist: %s has negative stock %.3f", entry.getKey().getFaoName(), stocklevel));
+				return true;
+			}
+		}
+		LogWriter.println(String.format("No negative stocks found"));
+		return false;
+	}
+
+	/*	public double findMaxPriceDiff(Map<CropType, Double> previousExportPrices) {
+		if (previousExportPrices == null)
+			return Double.MAX_VALUE;
+
+		double maxSoFar = 0;
+		CropType crop = null;
+
+		for (Entry<CropType, GlobalPrice> entry : worldPrices.entrySet()) {
+			Double previousP = previousExportPrices.get(entry.getKey());
+
+			if (previousP == null)
+				return Double.MAX_VALUE;
+
+			double diffThisTime = Math.abs(previousP - entry.getValue().getExportPrice());
+			if (diffThisTime > maxSoFar) {
+				maxSoFar = diffThisTime;
+				crop = entry.getKey();
+			}
+		}
+
+		LogWriter.println("findMaxPriceDiff: found " + maxSoFar + " for " + crop);
+		return maxSoFar;
+	} */
+}
diff --git a/src/ac/ed/lurg/ModelConfig.java b/src/ac/ed/lurg/ModelConfig.java
index 69c9cea0015383ec9f061e6ebfab7698b43950c8..8c7d7116f2aa06191c0ab73e5bb8079d29e397ac 100755
--- a/src/ac/ed/lurg/ModelConfig.java
+++ b/src/ac/ed/lurg/ModelConfig.java
@@ -1,480 +1,480 @@
-package ac.ed.lurg;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.Enumeration;
-import java.util.Properties;
-import java.lang.Long;
-
-import ac.ed.lurg.shock.parameterShocksReader;
-import ac.ed.lurg.types.ModelFitType;
-import ac.ed.lurg.types.PriceType;
-import ac.ed.lurg.utils.LogWriter;
-
-public class ModelConfig {
-	
-	private Properties configFile;
-	private static ModelConfig modelConfig;
-	public static final String CONFIG_FILE = System.getProperty("CONFIG_FILE");
-	
-	private static parameterShocksReader shocksReader;
-	
-	private ModelConfig() {
-		configFile = new Properties();
-		try {
-			System.out.println("Config. file is " + CONFIG_FILE);
-			if (CONFIG_FILE != null)
-				configFile.load(new FileInputStream(CONFIG_FILE));
-		}
-		catch (IOException e) {
-			System.err.println("Problems reading config file");
-			System.err.println(e.getMessage());
-		}
-	}
-
-	public static String getSetupDetails() {
-		String buildVerion = System.getProperty("BUILDVER");
-		StringBuffer sb = new StringBuffer("Build version: " + buildVerion + "\n");
-
-		Properties props = getModelConfig().configFile;
-		Enumeration<?> em = props.keys();
-		while(em.hasMoreElements()) {
-			String str = (String) em.nextElement();
-			sb.append(str + ": " + props.get(str) + "\n");
-		}
-
-		return sb.toString();
-	}
-	
-	private static ModelConfig getModelConfig() {
-		if (modelConfig == null)
-			modelConfig = new ModelConfig();
-
-		return modelConfig;
-	}
-	
-	private static String getProperty(String prop) {
-		return getModelConfig().getProp(prop);
-	}
-	private static String getProperty(String prop, String defaultString) {
-		String propValue = getProperty(prop);
-		return propValue == null ? defaultString : propValue;
-	}
-	private String getProp(String prop) {
-		return configFile.getProperty(prop);
-	}
-	
-	private static Integer getIntProperty(String prop, Integer defaultInt) {
-		Integer propValue = getModelConfig().getIntProp(prop);
-		return propValue == null ? defaultInt : propValue;
-	}
-	private Integer getIntProp(String prop) {
-		String v = configFile.getProperty(prop);
-		return v==null ? null : Integer.valueOf(v);
-	}
-	
-	@SuppressWarnings("unused")
-	private static Long getLongProperty(String prop, Long defaultLong) {
-		Long propValue = getModelConfig().getLongProp(prop);
-		return propValue == null ? defaultLong : propValue;
-	}
-	private Long getLongProp(String prop) {
-		String v = configFile.getProperty(prop);
-		return v==null ? null : Long.valueOf(v);
-	}
-	
-	private static Double getDoubleProperty(String prop, Double defaultDouble) {
-		Double propValue = getModelConfig().getDoubleProp(prop);
-		return propValue == null ? defaultDouble : propValue;
-	}
-	private Double getDoubleProp(String prop) {
-		String v = configFile.getProperty(prop);
-		return v==null ? null :  Double.valueOf(v);
-	}
-	
-	private static Boolean getBooleanProperty(String prop, Boolean defaultBoolean) {
-		return getModelConfig().getBooleanProp(prop, defaultBoolean);
-	}
-	private boolean getBooleanProp(String prop, Boolean defaultBoolean) {
-		String v = configFile.getProperty(prop);
-		return v==null ? defaultBoolean :  Boolean.valueOf(v);
-	}
-	
-	public static double updateParameterForShocks(int year, String parameter) {
-
-		Double value = null;
-		try {
-			value =  getModelConfig().getClass().getField(parameter).getDouble(parameter);
-
-			if(SHOCKS_POSSIBLE) {
-
-				if (shocksReader == null) {
-					shocksReader = new parameterShocksReader(",");
-					shocksReader.read(SHOCKS_PARAMETER_FILE);
-				}
-				Double updatedValue = shocksReader.queryForParameter(year, parameter);
-
-				if(updatedValue !=null) 
-					value = updatedValue;
-			}
-
-		}
-		catch (IllegalAccessException | NoSuchFieldException e) {
-			LogWriter.printlnError("cannot find parameter in model config to shock: " + parameter);
-			LogWriter.print(e);
-		}
-		return value;
-	}
-
-		
-	public static final boolean SUPPRESS_STD_OUTPUT = getBooleanProperty("SUPPRESS_STD_OUTPUT", Boolean.FALSE);
-
-	// Directory information
-	public static final String BASE_DIR = getProperty("BASE_DIR");  // this must to be set in config file
-    public static final String OUTPUT_DIR = getProperty("OUTPUT_DIR", ".");
-	public static final String TEMP_DIR = getProperty("TEMP_DIR", OUTPUT_DIR + File.separator + "GamsTmp");
-	public static final String DATA_DIR = getProperty("DATA_DIR", BASE_DIR + File.separator + "data");
-	public static final String GAMS_DIR = getProperty("GAMS_DIR", BASE_DIR + File.separator + "GAMS");
-	public static final boolean CLEANUP_GAMS_DIR = getBooleanProperty("CLEANUP_GAMS_DIR", false);
-
-	public static final boolean ORIG_LEAST_COST_MIN = getBooleanProperty("ORIG_LEAST_COST_MIN", true);
-	public static final String GAMS_MODEL_NAME = getProperty("GAMS_MODEL_NAME", ORIG_LEAST_COST_MIN==true ? "IntExtOpt.gms" : "LUOpt.gms");
-	public static final String GAMS_MODEL = getProperty("GAMS_MODEL", GAMS_DIR + File.separator + GAMS_MODEL_NAME);
-	public static final String DEMAND_GAMS_MODEL = getProperty("DEMAND_GAMS_MODEL", GAMS_DIR + File.separator + "elasticDemand.gms");
-	public static final String DEMAND_PARAM_FILE = getProperty("DEMAND_PARAM_FILE", DATA_DIR + File.separator + "DemandParamConv.gdx");
-
-	// Country (non-gridded) data
-	public static final boolean DEMAND_FROM_FILE = getBooleanProperty("DEMAND_FROM_FILE", false); // used in hindcasting
-	public static final boolean PRICE_ELASTIC_DEMAND = getBooleanProperty("PRICE_ELASTIC_DEMAND", true);
-	public static final boolean DONT_REBASE_DEMAND = getBooleanProperty("DONT_REBASE_DEMAND", false);;
-	public static final String DEMAND_CURVES_FILE = getProperty("DEMAND_CURVES_FILE", DATA_DIR + File.separator + "com_curves.csv");  // either DEMAND_CURVES_FILE or DEMAND_CONSUMPTION_FILE is used, but not both
-	public static final String DEMAND_CONSUMPTION_FILE = getProperty("DEMAND_CONSUMPTION_FILE", DATA_DIR + File.separator + "hist_comsump.csv");
-	public static final PriceType PRICE_CALCULATION = PriceType.findByName(getProperty("PRICE_CALCULATION", "weightedImportsExports"));
-	public static final String SSP_FILENAME = getProperty("SSP_FILENAME", "ssp.csv");
-	public static final String SSP_FILE = getProperty("SSP_FILE", DATA_DIR + File.separator + SSP_FILENAME);	
-	public static final String BASELINE_CONSUMP_FILE = DATA_DIR + File.separator + "base_consump.csv";
-	public static final String CALORIE_PER_T_FILE = DATA_DIR + File.separator + "calories_per_t.csv";
-	public static final String COUNTRY_CODES_FILE = DATA_DIR + File.separator + "country_codes4.csv";
-	public static final String COUNTRY_DATA_FILE = DATA_DIR + File.separator + "country_data.csv";
-	public static final String NET_IMPORTS_FILE = DATA_DIR + File.separator + "net_imports.csv";
-	public static final String BIOENERGY_1GEN_BASE_DEMAND_FILE = DATA_DIR + File.separator + "bio_demand.csv";
-	public static final String BIOENERGY_2GEN_DEMAND_FILENAME = getProperty("BIOENERGY_2GEN_DEMAND_FILENAME", "bioenergy_gen2_iiasa.csv");
-	public static final String BIOENERGY_2GEN_DEMAND_FILE = getProperty("BIOENERGY_2GEN_DEMAND_FILE", DATA_DIR + File.separator + BIOENERGY_2GEN_DEMAND_FILENAME);
-	public static final String BIOENERGY_1GEN_FUTURE_DEMAND_FILENAME = getProperty("BIOENERGY_FUTURE_DEMAND_FILENAME", "bioenergy_gen1.csv");
-	public static final String BIOENERGY_1GEN_FUTURE_DEMAND_FILE = getProperty("BIOENERGY_FUTURE_DEMAND_FILE", DATA_DIR + File.separator + BIOENERGY_1GEN_FUTURE_DEMAND_FILENAME);
-	public static final String TRADE_BARRIERS_FILENAME = getProperty("TRADE_BARRIERS_FILENAME", "tradeBarriers.csv");
-	public static final String TRADE_BARRIERS_FILE = getProperty("TRADE_BARRIERS_FILE", DATA_DIR + File.separator + TRADE_BARRIERS_FILENAME);
-	public static final String TRADE_DISTORTIONS_FILE = DATA_DIR + File.separator + "tradeDistortions.csv";
-	public static final String STOCKS_FILE = DATA_DIR + File.separator + "global_stocks.csv";
-	public static final String FAO_CONSUMPTION_FILE = DATA_DIR + File.separator + "fao_consump.csv";
-	public static final String COUNTRY_GROUPING_FILE = DATA_DIR + File.separator + "country_groups.csv";
-	public static final String OTHER_WATER_USES_FILE = DATA_DIR + File.separator + "other_water_uses.csv";
-	public static final String BASE_DEMAND_FRACT_FILE = DATA_DIR + File.separator + "base_demand_fracts.csv";
-	public static final String SHOCKS_PARAMETER_FILE = OUTPUT_DIR + File.separator+ "shocks.csv";
-	public static final String SUBSIDY_RATE_FILENAME = getProperty("SUBSIDY_RATE_FILENAME", "subsidyrates.csv");
-	public static final String SUBSIDY_RATE_FILE = getProperty("SUBSIDY_RATE_FILE", DATA_DIR + File.separator + SUBSIDY_RATE_FILENAME);
-	public static final String ANIMAL_RATES_FILE = DATA_DIR + File.separator + "animal_numbers.csv";;
-	public static final String INITIAL_CONSUMER_PRICE_FILE = DATA_DIR + File.separator + "consumerprices.csv";;
-	public static final String GDP_FRACTIONS_FILE = DATA_DIR + File.separator + "agriculturalGdpFraction.csv";
-
-	// yield data
-	public static final String YIELD_DIR_BASE = getProperty("YIELD_DIR_BASE");
-	public static final String YIELD_DIR_TOP = getProperty("YIELD_DIR_TOP");
-	public static final String YIELD_DIR = getProperty("YIELD_DIR", YIELD_DIR_BASE + File.separator + YIELD_DIR_TOP);
-	public static final int LPJG_MONITOR_TIMEOUT_SEC = getIntProperty("LPJG_MONITOR_TIMEOUT", 60*60*2);
-	public static final String ANPP_FILENAME = getProperty("ANPP_FILENAME", "anpp.out");
-	public static final String YIELD_FILENAME = getProperty("YIELD_FILENAME", "yield.out");
-	public static final boolean PASTURE_FERT_RESPONSE_FROM_LPJ = getBooleanProperty("PASTURE_FERT_RESPONSE_FROM_LPJ", false);;
-
-	public static final double CALIB_FACTOR_CEREAL_C3 = getDoubleProperty("CALIB_FACTOR_CEREAL_C3", 1.046);
-	public static final double CALIB_FACTOR_CEREAL_C4 = getDoubleProperty("CALIB_FACTOR_CEREAL_C4", 0.654);
-	public static final double CALIB_FACTOR_MISCANTHUS = getDoubleProperty("CALIB_FACTOR_MISCANTHUS", 2.148);
-	public static final double CALIB_FACTOR_RICE = getDoubleProperty("CALIB_FACTOR_RICE", 0.972);
-	public static final double CALIB_FACTOR_OILCROPS = getDoubleProperty("CALIB_FACTOR_OILCROPS", 0.578);
-	public static final double CALIB_FACTOR_PULSES = getDoubleProperty("CALIB_FACTOR_PULSES", 0.686);
-	public static final double CALIB_FACTOR_STARCHY_ROOTS = getDoubleProperty("CALIB_FACTOR_STARCHY_ROOTS",4.560);
-	public static final double CALIB_FACTOR_FRUITVEG = getDoubleProperty("CALIB_FACTOR_FRUITVEG",3.526);
-	public static final double CALIB_FACTOR_SUGAR = getDoubleProperty("CALIB_FACTOR_SUGAR", 11.909);
-
-	// These are production prices in PLUM style feed equivalent terms
-	public static final double INITIAL_PRICE_SHIFT = getDoubleProperty("INITIAL_PRICE_SHIFT", 1.0);
-	public static final double INITAL_PRICE_WHEAT = getDoubleProperty("INITAL_PRICE_WHEAT", 0.157 * ModelConfig.INITIAL_PRICE_SHIFT);
-	public static final double INITAL_PRICE_MAIZE = getDoubleProperty("INITAL_PRICE_MAIZE", 0.152 * ModelConfig.INITIAL_PRICE_SHIFT);
-	public static final double INITAL_PRICE_RICE = getDoubleProperty("INITAL_PRICE_RICE", 0.182 * ModelConfig.INITIAL_PRICE_SHIFT);
-	public static final double INITAL_PRICE_OILCROPS = getDoubleProperty("INITAL_PRICE_OILCROPS", (0.820 * .4 + 0.314 * .6) * 0.3 * ModelConfig.INITIAL_PRICE_SHIFT);
-	public static final double INITAL_PRICE_PULSES = getDoubleProperty("INITAL_PRICE_PULSES", 0.2 * ModelConfig.INITIAL_PRICE_SHIFT);
-	public static final double INITAL_PRICE_STARCHYROOTS = getDoubleProperty("INITAL_PRICE_STARCHYROOTS", 0.1 * ModelConfig.INITIAL_PRICE_SHIFT);
-	public static final double INITAL_PRICE_MONOGASTRICS = getDoubleProperty("INITAL_PRICE_MONOGASTRICS", 0.4 * 0.5 * ModelConfig.INITIAL_PRICE_SHIFT); // quantities is in feed equivalent term (0.4 is weighted average price per feed, and 0.5 accounts for mark-up for additional processing)
-	public static final double INITAL_PRICE_RUMINANTS = getDoubleProperty("INITAL_PRICE_RUMINANTS", 0.1 * 0.6 * ModelConfig.INITIAL_PRICE_SHIFT); // quantities is in feed equivalent term
-	public static final double INITAL_PRICE_ENERGYCROPS = getDoubleProperty("INITAL_PRICE_ENERGYCROPS", 0.04 * ModelConfig.INITIAL_PRICE_SHIFT);
-	public static final double INITAL_PRICE_FRUITVEG = getDoubleProperty("INITAL_PRICE_FRUITVEG", 0.1 * ModelConfig.INITIAL_PRICE_SHIFT);
-	public static final double INITAL_PRICE_SUGAR = getDoubleProperty("INITAL_PRICE_SUGAR", 0.02 * ModelConfig.INITIAL_PRICE_SHIFT);
-
-	// These are initial demand system prices in 2000 kcal terms
-	public static final double INITAL_DEMAND_PRICE_CEREALS = getDoubleProperty("INITAL_DEMAND_PRICE_CEREALS", 120.2365);
-	public static final double INITAL_DEMAND_PRICE_OILCROPS_PULSES = getDoubleProperty("INITAL_DEMAND_PRICE_OILCROPS_PULSES", 147.4032);
-	public static final double INITAL_DEMAND_PRICE_STARCHYROOTS = getDoubleProperty("INITAL_DEMAND_PRICE_STARCHYROOTS", 543.5512);
-	public static final double INITAL_DEMAND_PRICE_MONOGASTRICS = getDoubleProperty("INITAL_DEMAND_PRICE_MONOGASTRICS", 1243.899);
-	public static final double INITAL_DEMAND_PRICE_RUMINANTS = getDoubleProperty("INITAL_DEMAND_PRICE_RUMINANTS", 1043.901);
-	public static final double INITAL_DEMAND_PRICE_FRUITVEG = getDoubleProperty("INITAL_DEMAND_PRICE_FRUITVEG", 3381.178);
-	public static final double INITAL_DEMAND_PRICE_SUGAR = getDoubleProperty("INITAL_DEMAND_PRICE_SUGAR", 168.9247);
-
-	// Spatial (gridded) data
-	public static final double CELL_SIZE_X = getDoubleProperty("CELL_SIZE_X", 0.5);
-	public static final double CELL_SIZE_Y = getDoubleProperty("CELL_SIZE_Y", CELL_SIZE_X);
-	public static final String SPATIAL_DIR_NAME = getProperty("SPATIAL_DIR_NAME", "halfdeg");
-	public static final String SPATIAL_DATA_DIR = getProperty("SPATIAL_DATA_DIR", DATA_DIR + File.separator + SPATIAL_DIR_NAME);
-	public static final String INITAL_LAND_COVER_FILENAME = getProperty("INITAL_LAND_COVER_FILENAME", "hurtt_2010.txt");
-	public static final String INITAL_LAND_COVER_FILE = SPATIAL_DATA_DIR + File.separator + INITAL_LAND_COVER_FILENAME;
-	public static final String COUNTRY_BOUNDARY_FILE = SPATIAL_DATA_DIR + File.separator + "country_boundaries.asc";
-	public static final String IRRIGATION_COST_FILE = SPATIAL_DATA_DIR + File.separator + "irrigation_cost.asc";
-	public static final String IRRIGATION_CONSTRAINT_FILE = SPATIAL_DATA_DIR + File.separator + "blue_water_available_pseudoCRU_rcp8p5_2004_2013_grid_allhdyro_mm.txt";
-	public static final String FPU_BOUNDARIES_FILE = SPATIAL_DATA_DIR + File.separator + "FPU.asc";
-	public static final String FPU_GROUPING_FILE = SPATIAL_DATA_DIR + File.separator + "fpuGrouping.txt";
-	public static final String IRRIG_MAX_WATER_FILENAME = getProperty("IRRIG_MAX_WATER_FILENAME", "gsirrigation.out");
-	public static final String IRRIG_RUNOFF_FILE = getProperty("IRRIG_RUNOFF_FILE", "tot_runoff.out");
-	public static final String PROTECTED_AREAS_FILE = getProperty("PROTECTED_AREAS_FILE",SPATIAL_DATA_DIR + File.separator + "protectedAreas2019.asc");
-	public static final String HALF_EARTH_FILE = getProperty("HALF_EARTH_FILE",SPATIAL_DATA_DIR + File.separator + "global_biodiversity_priorities50perc.asc");
-	public static final String HIGH_SLOPE_AREAS_FILE = SPATIAL_DATA_DIR + File.separator + "maxcropfrac2.txt";
-	public static final String YIELDSHOCK_MAP_DIR = SPATIAL_DATA_DIR + File.separator + "yieldshockmaps";
-	public static final String YIELDSHOCKS_PARAMETER_FILE = getProperty("YIELDSHOCKS_PARAMETER_FILE", OUTPUT_DIR + File.separator+ "yieldshocks.csv");
-	public static final String PRICESHOCKS_PARAMETER_FILE = getProperty("PRICESHOCKS_PARAMETER_FILE", OUTPUT_DIR + File.separator+ "priceshocks.csv");
-	public static final String EXPORT_RESTRICTIONS_FILE = getProperty("EXPORT_RESTRICTIONS_FILE", OUTPUT_DIR + File.separator+ "exportrestictions.csv");
-	
-	// Wood/carbon data
-	public static final String FOREST_DIR = SPATIAL_DATA_DIR + File.separator + "forestry";
-	public static final String WOOD_AND_CARBON_DIR = getProperty("WOOD_AND_CARBON_DIR");
-	//public static final String CARBON_FLUX_FILE = FOREST_DIR + File.separator + "carbon_flux_";
-	public static final String WOOD_YIELD_FRST_TO_AGRI_FILENAME = "wood_yield_forest_to_agri.dat";
-	public static final String WOOD_YIELD_FRST_TO_FRST_FILENAME = "wood_yield_forest_to_forest.dat";
-	public static final String WOOD_YIELD_NTRL_TO_AGRI_FILENAME = "wood_yield_ntrl_to_agri.dat";
-	public static final String WOOD_YIELD_NTRL_TO_FRST_FILENAME = "wood_yield_ntrl_to_forest.dat";
-	public static final String CARBON_LUC_FILENAME = "carbon_luc.out";
-	public static final String CARBON_NEE_FILENAME = "carbon_nee.out";
-	public static final String NATURAL_FOREST_GROWTH_FILENAME = "growth_natural.out";
-	public static final String LAND_COVER_AGE_DIST_FILENAME = SPATIAL_DATA_DIR + File.separator + "land_cover_age_dist.txt";
-	public static final int WOOD_AND_CARBON_TIMESTEP_SIZE = getIntProperty("WOOD_AND_CARBON_TIMESTEP_SIZE", 20); // years
-	public static final double WOOD_YIELD_CALIB_FACTOR = getDoubleProperty("WOOD_YIELD_CALIB_FACTOR", 8.0); 
-	public static final int CARBON_WOOD_MAX_TIME = getIntProperty("CARBON_WOOD_AGE_CLASSES", 165);
-
-	// Output
-	public static final String LAND_COVER_OUTPUT_FILE = OUTPUT_DIR + File.separator + "lc.txt";
-	public static final String PRICES_OUTPUT_FILE = OUTPUT_DIR + File.separator + "prices.txt";
-	public static final String DEMAND_OUTPUT_FILE = OUTPUT_DIR + File.separator + "demand.txt";
-	public static final String DOMESTIC_OUTPUT_FILE = OUTPUT_DIR + File.separator + "domestic.txt";
-	public static final String COUNTRY_DEMAND_FILE = OUTPUT_DIR + File.separator + "countryDemand.txt";
-	public static final String DEMAND_OPTIMISATION_OUTPUT_FILE = OUTPUT_DIR + File.separator + "countryDemandOpt.txt";
-	public static final String FOOD_BALANCE_SHEET_FILE = OUTPUT_DIR + File.separator + "fbs.txt";
-	public static final String ANIMAL_NUMBERS_OUTPUT_FILE = OUTPUT_DIR + File.separator + "animals.txt";;
-
-	public static final boolean OUTPUT_FOR_LPJG = getBooleanProperty("OUTPUT_FOR_LPJG", true);
-	public static final boolean INTERPOLATE_OUTPUT_YEARS = getBooleanProperty("INTERPOLATE_OUTPUT_YEARS", true);	
-	
-	// Calibration related stuff
-	public static final boolean IS_CALIBRATION_RUN = getBooleanProperty("IS_CALIBRATION_RUN", false);
-	public static final String CALIB_DIR = IS_CALIBRATION_RUN ? OUTPUT_DIR : getProperty("CALIB_DIR", OUTPUT_DIR);
-	public static final int END_FIRST_STAGE_CALIBRATION = getIntProperty("END_FIRST_STAGE_CALIBRATION", 10);
-	public static final String SERIALIZED_LAND_USE_FILENAME = "landUseRaster.ser";
-	public static final String SERIALIZED_CROP_USAGE_FILENAME = "countryCropUsages.ser";
-	public static final String SERIALIZED_INTERNATIONAL_MARKET_FILENAME = "internationalMarket.ser";
-	public static final String SERIALIZED_WOOD_USAGE_FILENAME = "countryWoodUsage.ser";
-	public static final String SERIALIZED_LAND_COVER_FILENAME = "landCoverData.dat";
-	public static final String SERIALIZED_LAND_USE_FILE = CALIB_DIR + File.separator +  SERIALIZED_LAND_USE_FILENAME;
-	public static final String SERIALIZED_CROP_USAGE_FILE = CALIB_DIR + File.separator +  SERIALIZED_CROP_USAGE_FILENAME;
-	public static final String SERIALIZED_INTERNATIONAL_MARKET_FILE = CALIB_DIR + File.separator +  SERIALIZED_INTERNATIONAL_MARKET_FILENAME;
-	public static final String SERIALIZED_LAND_COVER_FILE = CALIB_DIR + File.separator + SERIALIZED_LAND_COVER_FILENAME;
-	public static final String SERIALIZED_WOOD_USAGE_FILE = CALIB_DIR + File.separator + SERIALIZED_WOOD_USAGE_FILENAME;
-	public static final String CHECKPOINT_LAND_USE_FILE = getProperty("CHECKPOINT_LAND_USE_FILE", OUTPUT_DIR + File.separator +  SERIALIZED_LAND_USE_FILENAME);
-	public static final String CHECKPOINT_CROP_USAGE_FILE = getProperty("CHECKPOINT_CROP_USAGE_FILE", OUTPUT_DIR + File.separator +  SERIALIZED_CROP_USAGE_FILENAME);
-	public static final String CHECKPOINT_INTERNATIONAL_MARKET_FILE = getProperty("CHECKPOINT_INTERNATIONAL_MARKET_FILE", OUTPUT_DIR + File.separator +  SERIALIZED_INTERNATIONAL_MARKET_FILENAME);
-	public static final String CHECKPOINT_LAND_COVER_FILE = getProperty("CHECKPOINT_LAND_COVER_FILE", OUTPUT_DIR + File.separator +  SERIALIZED_LAND_COVER_FILENAME);
-	public static final String CHECKPOINT_WOOD_USAGE_FILE = getProperty("CHECKPOINT_WOOD_USAGE_FILE", OUTPUT_DIR + File.separator +  SERIALIZED_WOOD_USAGE_FILENAME);
-	
-	public static final boolean MARKET_ADJ_PRICE = getBooleanProperty("MARKET_ADJ_PRICE", true);
-	public static final boolean CHANGE_YIELD_DATA_YEAR = IS_CALIBRATION_RUN ? false : getBooleanProperty("CHANGE_YIELD_DATA_YEAR", true);
-	public static final String CLUSTERED_YIELD_FILE = getProperty("CLUSTERED_YIELD_FILE", CALIB_DIR + File.separator + "cluster.asc");
-	public static final boolean GENERATE_NEW_YIELD_CLUSTERS = getBooleanProperty("GENERATE_NEW_YIELD_CLUSTERS", IS_CALIBRATION_RUN);
-	
-
-	// Temporal configuration
-	public static final int START_TIMESTEP = getIntProperty("START_TIMESTEP", 0);
-	public static final int END_TIMESTEP = getIntProperty("END_TIMESTEP", 90);
-	public static final int TIMESTEP_SIZE = getIntProperty("TIMESTEP_SIZE", 1);
-	public static final int BASE_YEAR = getIntProperty("BASE_YEAR", 2010);
-
-	// Import export limits
-	public static final double ANNUAL_MAX_IMPORT_CHANGE = getDoubleProperty("ANNUAL_MAX_IMPORT_CHANGE", 0.05);
-	public static final double MAX_IMPORT_CHANGE = getDoubleProperty("MAX_IMPORT_CHANGE", ANNUAL_MAX_IMPORT_CHANGE*TIMESTEP_SIZE);
-
-	// Fertiliser application rates in kg/ha
-	public static final double MIN_FERT_AMOUNT = getDoubleProperty("MIN_FERT_AMOUNT", 0.0);
-	public static final double MID_FERT_AMOUNT = getDoubleProperty("MID_FERT_AMOUNT", 200.0);
-	public static final double MAX_FERT_AMOUNT = getDoubleProperty("MAX_FERT_AMOUNT", 1000.0);
-	public static final int FERT_AMOUNT_PADDING = getIntProperty("FERT_AMOUNT_PADDING", 4);;
-
-	// SSP shift parameters
-	public static final double SSP_POPULATION_FACTOR = getDoubleProperty("SSP_POPULATION_FACTOR", 1.0);
-	public static final double SSP_GDP_PC_FACTOR = getDoubleProperty("SSP_GDP_PC_FACTOR", 1.0);
-
-	// Other model parameters
-	public static final boolean CHANGE_DEMAND_YEAR = IS_CALIBRATION_RUN ? false : getBooleanProperty("CHANGE_DEMAND_YEAR", true);
-	public static final double DIETARY_CLOSURE = getDoubleProperty("DIETARY_CLOSURE", 0.25); // Amount diet converges in DIETARY_CLOSURE_GDP_CHANGE rate of GDP shift
-	public static final double DIETARY_CLOSURE_GDP_CHANGE = getDoubleProperty("DIETARY_CLOSURE_GDP_CHANGE", 2.0); // 2 is double of GDP
-	public static final double DIETARY_CLOSURE_PARAM = getDoubleProperty("DIETARY_CLOSURE_PARAM", Math.log((1-DIETARY_CLOSURE))/DIETARY_CLOSURE_GDP_CHANGE); // Zero is no dietary closure, number specifies closure rate for changes in GDP per capita, e.g. Math.log(0.5)/2
-	public static final String SSP_SCENARIO = getProperty("SSP_SCENARIO", "SSP1_v9_130325");
-	public static final ModelFitType DEMAND_ANIMAL_PROD_FIT = ModelFitType.findByName(getProperty("DEMAND_ANIMAL_PROD_FIT", "loglinear"));
-	public static final ModelFitType DEMAND_NON_ANIMAL_PROD_FIT = ModelFitType.findByName(getProperty("DEMAND_NON_ANIMAL_PROD_FIT", "loglinear"));
-	public static final boolean LIMIT_DEMAND_FRACTION = getBooleanProperty("LIMIT_DEMAND_FRACTION", true);
-	public static final boolean DEMAND_FRACT_BY_COST = getBooleanProperty("DEMAND_FRACT_BY_COST", false);;
-	public static final double MAX_INCOME_PROPORTION_FOOD_SPEND = getDoubleProperty("MAX_INCOME_PROPORTION_FOOD_SPEND", 0.7);
-	
-	public static final double PASTURE_HARVEST_FRACTION = getDoubleProperty("PASTURE_HARVEST_FRACTION", 0.5);
-	public static final double MEAT_EFFICIENCY = getDoubleProperty("MEAT_EFFICIENCY", 1.0);  // 'meat' is includes feed conversion ratio already, this is tech. change or similar
-	public static final double IRRIGIATION_EFFICIENCY = getDoubleProperty("IRRIGIATION_EFFICIENCY", 0.5);
-	public static final int ELLIOTT_BASEYEAR = 2010;
-	public static final double ENVIRONMENTAL_WATER_CONSTRAINT = getDoubleProperty("ENVIRONMENTAL_WATER_CONSTRAINT", 0.5); // change with care, as due to normalisation it might not have the impact you first imagine
-	public static final double OTHER_WATER_USE_FACTOR = getDoubleProperty("OTHER_WATER_USE_FACTOR", 1.0);
-	public static final boolean USE_BLUE_WATER_FILE_IRRIG_CONSTRAINT = getBooleanProperty("USE_BLUE_WATER_FILE_IRRIG_CONSTRAINT", false);;
-
-	public static final double LAND_CHANGE_COST = getDoubleProperty("LAND_CHANGE_COST", 0.2);
-	public static final double CROP_TO_PASTURE_COST_FACTOR = getDoubleProperty("CROP_TO_PASTURE_COST_FACTOR", 1.0);
-	public static final double AGRI_LAND_EXPANSION_COST_FACTOR = getDoubleProperty("AGRI_LAND_EXPANSION_COST_FACTOR", 1.0);
-
-	public static final double CROP_INCREASE_COST = getDoubleProperty("CROP_INCREASE_COST", 0.05 * LAND_CHANGE_COST * AGRI_LAND_EXPANSION_COST_FACTOR);
-	public static final double PASTURE_DECREASE_COST = getDoubleProperty("PASTURE_DECREASE_COST", LAND_CHANGE_COST);
-	public static final double CROP_DECREASE_COST = getDoubleProperty("CROP_DECREASE_COST", 1.65 * LAND_CHANGE_COST);
-	public static final double PASTURE_INCREASE_COST = getDoubleProperty("PASTURE_INCREASE_COST", 0.35 * LAND_CHANGE_COST * CROP_TO_PASTURE_COST_FACTOR * AGRI_LAND_EXPANSION_COST_FACTOR);
-	public static final double AGRI_EXPANSION_COST_BASE = getDoubleProperty("AGRI_EXPANSION_COST_BASE", 0.03 * LAND_CHANGE_COST * AGRI_LAND_EXPANSION_COST_FACTOR);
-	public static final double AGRI_EXPANSION_COST_BASE_MANAGED_FOREST = getDoubleProperty("AGRI_EXPANSION_COST_BASE_MANAGED_FOREST", 0.5 * LAND_CHANGE_COST * AGRI_LAND_EXPANSION_COST_FACTOR);
-
-	public static final double TECHNOLOGY_CHANGE_ANNUAL_RATE = getDoubleProperty("TECHNOLOGY_CHANGE_ANNUAL_RATE", 0.002);
-	public static final int TECHNOLOGY_CHANGE_START_STEP = getIntProperty("TECHNOLOGY_CHANGE_START_STEP", 0);
-
-	public static final boolean USE_BIOENERGY_TRAJECTORY = getBooleanProperty("USE_BIOENERGY_TRAJECTORY", true);  // false is the old style, i.e. BIOENERGY_CHANGE_ANNUAL_RATE, BIOENERGY_CHANGE_START_YEAR and BIOENERGY_CHANGE_END_YEAR
-	// original way to specify changes in bioenergy
-	public static final double BIOENERGY_CHANGE_ANNUAL_RATE = IS_CALIBRATION_RUN ? 0.0 : getDoubleProperty("BIOENERGY_CHANGE_ANNUAL_RATE", 0.10); // 6.2/2.31/20
-	public static final int BIOENERGY_CHANGE_START_YEAR = getIntProperty("BIOENERGY_CHANGE_START_YEAR", 2010);
-	public static final int BIOENERGY_CHANGE_END_YEAR = getIntProperty("BIOENERGY_CHANGE_END_YEAR", 2060);
-	// newer way
-	public static final boolean ENABLE_GEN2_BIOENERGY = getBooleanProperty("ENABLE_GEN2_BIOENERGY", true);
-	public static final String BIOENERGY_DEMAND_SCENARIO = getProperty("BIOENERGY_DEMAND_SCENARIO", "SSP2_RCP45");
-	public static final String GEN2_BIOENERGY_MODEL = getProperty("GEN2_BIOENERGY_MODEL", "ENSEMBLE-MEAN");
-	public static final double BIOENERGY_DEMAND_SHIFT = IS_CALIBRATION_RUN ? 1.0 : getDoubleProperty("BIOENERGY_DEMAND_SHIFT", 1.0);
-//	public static final double BIOENERGY_HEATING_VALUE_GJ_PER_T = getDoubleProperty("BIOENERGY_HEATING_VALUE_GJ_PER_T", 17.5); // GJ per t DM
-	
-	//Dietary stuff
-	public static final double RUMINANT_CHANGE_ANNUAL_RATE  = getDoubleProperty("RUMINANT_CHANGE_ANNUAL_RATE", 0.0);
-	public static final double MONOGASTRIC_CHANGE_ANNUAL_RATE  = getDoubleProperty("MONOGASTRIC_CHANGE_ANNUAL_RATE", 0.00);
-	public static final boolean CONSTANT_DIET_LOW_INCOME = getBooleanProperty("CONSTANT_DIET_LOW_INCOME", false);
-	public static final boolean CONSTANT_DIET_HIGH_INCOME  = getBooleanProperty("CONSTANT_DIET_HIGH_INCOME", false);
-	//below should all sum to zero
-	public static final double CEREALS_SUB_PROPORTION  = getDoubleProperty("CEREALS_SUB_PROPORTION", 0.3);
-	public static final double OILCROPSPULSES_SUB_PROPORTION  = getDoubleProperty("OILCROPSPULSES_SUB_PROPORTION", 0.25);
-	public static final double STARCHY_ROOTS_SUB_PROPORTION  = getDoubleProperty("STARCHY_ROOTS_SUB_PROPORTION", 0.25);
-	public static final double FRUITVEG_SUB_PROPORTION  = getDoubleProperty("FRUITVEG_SUB_PROPORTION", 0.1);
-	public static final double SUGAR_SUB_PROPORTION  = getDoubleProperty("SUGAR_SUB_PROPORTION", 0.1);
-
-	public static final double MARKET_LAMBA = getDoubleProperty("MARKET_LAMBA", 0.4); // controls international market price adjustment rate
-	public static final boolean PRICE_UPDATE_BY_MARKET_IMBALANCE = getBooleanProperty("PRICE_UPDATE_BY_MARKET_IMBALANCE", false);;
-	public static final double MAX_PRICE_INCREASE = getDoubleProperty("MAX_PRICE_INCREASE", 1.5);
-	public static final double MAX_PRICE_DECREASE = getDoubleProperty("MAX_PRICE_DECREASE", .75);
-	public static final int DEMAND_RECALC_MAX_ITERATIONS = IS_CALIBRATION_RUN ? 0 : getIntProperty("DEMAND_RECALC_MAX_ITERATIONS", 1);  // 0 is original behaviour
-	public static final boolean DEMAND_RECALC_ON_NEGATIVE_STOCK = IS_CALIBRATION_RUN ? false : getBooleanProperty("DEMAND_RECALC_ON_NEGATIVE_STOCK", false); 
-
-	public static final double POPULATION_AGGREG_LIMIT = getDoubleProperty("POPULATION_AGGREG_LIMIT", 30.0);  // in millions, smaller countries are aggregated on a regional basis
-	public static final boolean PREDEFINED_COUNTRY_GROUPING = getBooleanProperty("PREDEFINED_COUNTRY_GROUPING", true);
-
-	// 1536.7 Mha is all cropland in LUH2, or 1555.6 Mha from FAO.  country_data[Country =="World" & Year == 2011, list(arable+ perm_crops)]
-	// 1377.6 Mha is area of crops in FAO crop production.  con_prod_unadj[Country =="World" & Item %in% itemDetails[produced==TRUE]$Item & Year == 2011, sum(prod_area, na.rm=TRUE)]
-	// 1105.6 Mha is harvested in crops we represent.  crop_prod[Country =="World" & Item %in% itemGroupMapping$cropProdItem & Year == 2011, sum(area)]
-	// extract values by running AltLURead.R and common.R 
-	// So we do not represent crops covering (1341.911-1239.03)/1547.464=6.65% of cropland.  Additionally 10.3% of cropland is set aside, fallow or failed crops, 
-	public static final double UNHANDLED_CROP_RATE = getDoubleProperty("UNHANDLED_CROP_RATE", 0.0665);  // mostly forage crops
-	public static final double SETASIDE_RATE = getDoubleProperty("SETASIDE_RATE", 0.103);  // includes aside, fallow and failed cropland areas 
-	
-	public static final double OTHER_INTENSITY_COST = getDoubleProperty("OTHER_INTENSITY_COST", 0.8);
-	public static final double OTHER_INTENSITY_PARAM = getDoubleProperty("OTHER_INTENSITY_PARAM", 3.22);
-
-	public static final double IRRIG_COST_SCALE_FACTOR = getDoubleProperty("IRRIG_COST_SCALE_FACTOR", 0.0003);
-	public static final double IRRIG_COST_MULTIPLIER = getDoubleProperty("IRRIG_COST_MULTIPLIER", 1.0);
-	public static final double FERTILISER_COST_PER_T = getDoubleProperty("FERTILISER_COST_PER_T", 1.4); // $500/t, 18% N/t
-	public static final double FERTILISER_MAX_COST = FERTILISER_COST_PER_T * MAX_FERT_AMOUNT/1000;
-	
-	public static final double DOMESTIC_PRICE_MARKUP = getDoubleProperty("DOMESTIC_PRICE_MARKUP", 1.0);
-	public static final double TRANSPORT_LOSSES = getDoubleProperty("TRANSPORT_LOSSES", 0.05);  // in international trade
-	public static final double TRANSPORT_COST = getDoubleProperty("TRANSPORT_COST", 0.1);  // 10% transport cost
-	public static final double TRADE_BARRIER_FACTOR_DEFAULT = getDoubleProperty("TRADE_BARRIER_FACTOR_DEFAULT", 0.2);  // price factor in international trade, transport cost and real trade barriers
-	public static final double TRADE_BARRIER_MULTIPLIER = getDoubleProperty("TRADE_BARRIER_MULTIPLIER", 1.0);
-	public static final boolean ACTIVE_TRADE_BARRIERS = getBooleanProperty("ACTIVE_TRADE_BARRIERS", true);  // if set to true read in barrier information from file, otherwise use default as above
-	public static final double TRADE_BARRIER_ENERGY_CROPS = getDoubleProperty("TRADE_BARRIER_ENERGY_CROPS", 0.01);  // price factor in international trade, transport cost and real trade barriers
-	public static final boolean SHOCKS_POSSIBLE = getBooleanProperty("SHOCKS_POSSIBLE", false);
-	public static final double YIELD_SHOCK_MAGNIFIER = getDoubleProperty("YIELD_SHOCK_MAGNIFIER", 1.0);
-	
-	public static final boolean PROTECTED_AREAS_ENABLED = getBooleanProperty("PROTECTED_AREAS_ENABLED", true);
-	public static final double MIN_NATURAL_RATE = getDoubleProperty("MIN_NATURAL_RATE", 0.05);
-	public static final double MAX_CHINA_LAND_EXPANSION_RATE = getDoubleProperty("MAX_CHINA_LAND_EXPANSION_RATE", 0.011*0.4); // 1.1% max forest change 40% of natural land is forest
-
-	public static final boolean DEBUG_JUST_DEMAND_OUTPUT = getBooleanProperty("DEBUG_JUST_DEMAND_OUTPUT", false);
-	public static final boolean DEBUG_LIMIT_COUNTRIES = getBooleanProperty("DEBUG_LIMIT_COUNTRIES", false);
-	public static final String DEBUG_COUNTRY_NAME = getProperty("DEBUG_COUNTRY_NAME", "United States of America");
-	public static final String GAMS_COUNTRY_TO_SAVE = getProperty("GAMS_COUNTRY_TO_SAVE", "China");;
-	public static final boolean EXCLUDE_COUNTRIES_IN_LIST = getBooleanProperty("EXCLUDE_COUNTRIES_IN_LIST", false);
-	public static final String EXCLUDED_COUNTRIES_FILE = DATA_DIR + File.separator + "countries_excluded.csv";
-	
-	public static final double PASTURE_MAX_IRRIGATION_RATE = getDoubleProperty("DEFAULT_MAX_IRRIGATION_RATE", 50.0); // shouldn't need this but some areas crops don't have a value, but was causing them to be selected
-	public static final int LPJG_TIMESTEP_SIZE = getIntProperty("LPJG_TIMESTEP_SIZE", 5);
-	public static final int LPJ_YEAR_OFFSET = getIntProperty("LPJ_YEAR_OFFSET", 0);;
-	
-	public static final int NUM_YIELD_CLUSTERS = getIntProperty("NUM_YIELD_CLUSTERS", 8000);
-	public static final long RANDOM_SEED = getIntProperty("RANDOM_SEED", 1974329);  // any number will do
-	
-	public static final String CHECKPOINT_YEARS = getProperty("CHECKPOINT_YEARS", null); // 2020,2050
-	
-	// Protected areas forcing parameters
-	public static final boolean FORCE_PROTECTED_AREAS = IS_CALIBRATION_RUN ? false : getBooleanProperty("FORCE_PROTECTED_AREAS", false);
-	public static final int FORCE_PROTECTED_AREAS_START_YEAR  = getIntProperty("FORCE_PROTECTED_AREAS_START_YEAR", 2020);
-	public static final int FORCE_PROTECTED_AREAS_END_YEAR  = getIntProperty("FORCE_PROTECTED_AREAS_END_YEAR", 2040);
-	public static final double CONSTANT_PROTECTED_AREA_RATE = getDoubleProperty("CONSTANT_PROTECTED_AREA_RATE", Double.NaN);
-	public static final boolean HALFEARTH =  getBooleanProperty("HALFEARTH", false);
-
-	public static final boolean USE_CRAFTY_COUNTRIES = getBooleanProperty("USE_CRAFTY_COUNTRIES", false);
-	public static final String CRAFTY_PRODUCTION_DIR = getProperty("CRAFTY_PRODUCTION_DIR", OUTPUT_DIR + File.separator + "crafty");
-	
-	public static final boolean EXTRAPOLATE_YIELD_FERT_RESPONSE = getBooleanProperty("EXTRAPOLATE_YIELD_FERT_RESPONSE", false);
-	
-	public static final boolean ADJUST_DIET_PREFS = getBooleanProperty("ADJUST_DIET_PREFS", false);
-	public static final int DIET_CHANGE_START_YEAR = getIntProperty("DIET_CHANGE_START_YEAR", 2020);
-	public static final int DIET_CHANGE_END_YEAR = getIntProperty("DIET_CHANGE_END_YEAR", 2040);
-	public static final String TARGET_DIET_FILE = getProperty("TARGET_DIET_FILE", DATA_DIR + File.separator + "TargetDiet.txt");
-	public static final boolean APPLY_EXPORT_TAXES = getBooleanProperty("APPLY_EXPORT_TAXES", false);
-	public static final double EXPORT_TAX_RATE = getDoubleProperty("EXPORT_TAX_RATE", 1.0);
-	public static final double EXPORT_TAX_THRESHOLD = getDoubleProperty("EXPORT_TAX_THRESHOLD", 0.1);
-	
-	// Forestry and carbon market parameters
-	public static final String CONVERSION_COST_FILE = DATA_DIR + File.separator + "conversion_costs.csv"; // cost of converting from one land cover to another, $1000/ha
-	public static final String CARBON_DEMAND_FILENAME = getProperty("CARBON_DEMAND_FILENAME", "carbon_demand.csv");
-	public static final String CARBON_DEMAND_FILE = getProperty("CARBON_DEMAND_FILE", DATA_DIR + File.separator + CARBON_DEMAND_FILENAME);
-	public static final double INIT_CARBON_PRICE = getDoubleProperty("INIT_CARBON_PRICE", 0.02); // $1000/tC-eq
-	public static final String TIMBER_DEMAND_FILENAME = getProperty("TIMBER_DEMAND_FILENAME", "timber_demand.csv");
-	public static final String TIMBER_DEMAND_FILE = getProperty("TIMBER_DEMAND_FILE", DATA_DIR + File.separator + TIMBER_DEMAND_FILENAME);
-	public static final double INIT_WOOD_PRICE = getDoubleProperty("INIT_WOOD_PRICE", 0.4); // $1000/tC-eq
-	public static final double INIT_WOOD_STOCK = getDoubleProperty("INIT_WOOD_STOCK", 1000.0); // tC-eq
-	public static final double FOREST_MANAGEMENT_COST = getDoubleProperty("FOREST_MANAGEMENT_COST", 0.05); // $1000/ha/year
-	public static final double MANAGED_FOREST_INCREASE_COST = getDoubleProperty("MANAGED_FOREST_INCREASE_COST", 0.5 * LAND_CHANGE_COST); // $1000/ha
-	public static final double MANAGED_FOREST_DECREASE_COST = getDoubleProperty("MANAGED_FOREST_DECREASE_COST", 0.3 * LAND_CHANGE_COST); // $1000/ha
-	public static final double ROUNDWOOD_DEMAND_ELASTICITY = getDoubleProperty("ROUNDWOOD_DEMAND_ELASTICITY", -0.21956);
-	public static final double FUELWOOD_DEMAND_ELASTICITY = getDoubleProperty("FUELDWOOD_DEMAND_ELASTICITY", -0.22787);
-	public static final double FOREST_ESTABLISHMENT_COST = getDoubleProperty("FOREST_ESTABLISHMENT_COST", 2.0); // $1000/ha
-	public static final String WOOD_NET_IMPORTS_FILENAME = getProperty("WOOD_NET_IMPORTS_FILENAME", "wood_net_imports.csv");
-	public static final String WOOD_NET_IMPORTS_FILE = getProperty("WOOD_NET_IMPORTS_FILE", DATA_DIR + File.separator + WOOD_NET_IMPORTS_FILENAME);
-	public static final double WOOD_BIOMASS_CONVERSION_FACTOR = getDoubleProperty("WOOD_BIOMASS_CONVERSION_FACTOR", 2.688e-7); // m3 to MtC-eq p.16 [https://doi.org/10.5194/gmd-13-5425-2020] 
-	public static final double DISCOUNT_RATE = getDoubleProperty("DISCOUNT_RATE", 0.08);
-	public static final double VEGETATION_CLEARING_COST = getDoubleProperty("VEGETATION_CLEARING_COST", 0.005); //$1000/tC
-	public static final boolean CONVERSION_COSTS_FROM_FILE = getBooleanProperty("CONVERSION_COSTS_FROM_FILE", false);
-	public static final boolean IS_FORESTRY_ON = getBooleanProperty("IS_FORESTRY_ON", true);
-	public static final boolean IS_CARBON_ON = getBooleanProperty("IS_CARBON_ON", true);
-	public static final int CARBON_HORIZON = getIntProperty("CARBON_HORIZON", 30);
-	public static final double INFRASTRUCTURE_EXPANSION_COST = getDoubleProperty("INFRASTRUCTURE_EXPANSION_COST", 0.1); //$1000/tC
-	public static final double WOOD_TRADE_BARRIER = getDoubleProperty("WOOD_TRADE_BARRIER", 0.05); //$1000/tC
-}
+package ac.ed.lurg;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Properties;
+import java.lang.Long;
+
+import ac.ed.lurg.shock.parameterShocksReader;
+import ac.ed.lurg.types.ModelFitType;
+import ac.ed.lurg.types.PriceType;
+import ac.ed.lurg.utils.LogWriter;
+
+public class ModelConfig {
+	
+	private Properties configFile;
+	private static ModelConfig modelConfig;
+	public static final String CONFIG_FILE = System.getProperty("CONFIG_FILE");
+	
+	private static parameterShocksReader shocksReader;
+	
+	private ModelConfig() {
+		configFile = new Properties();
+		try {
+			System.out.println("Config. file is " + CONFIG_FILE);
+			if (CONFIG_FILE != null)
+				configFile.load(new FileInputStream(CONFIG_FILE));
+		}
+		catch (IOException e) {
+			System.err.println("Problems reading config file");
+			System.err.println(e.getMessage());
+		}
+	}
+
+	public static String getSetupDetails() {
+		String buildVerion = System.getProperty("BUILDVER");
+		StringBuffer sb = new StringBuffer("Build version: " + buildVerion + "\n");
+
+		Properties props = getModelConfig().configFile;
+		Enumeration<?> em = props.keys();
+		while(em.hasMoreElements()) {
+			String str = (String) em.nextElement();
+			sb.append(str + ": " + props.get(str) + "\n");
+		}
+
+		return sb.toString();
+	}
+	
+	private static ModelConfig getModelConfig() {
+		if (modelConfig == null)
+			modelConfig = new ModelConfig();
+
+		return modelConfig;
+	}
+	
+	private static String getProperty(String prop) {
+		return getModelConfig().getProp(prop);
+	}
+	private static String getProperty(String prop, String defaultString) {
+		String propValue = getProperty(prop);
+		return propValue == null ? defaultString : propValue;
+	}
+	private String getProp(String prop) {
+		return configFile.getProperty(prop);
+	}
+	
+	private static Integer getIntProperty(String prop, Integer defaultInt) {
+		Integer propValue = getModelConfig().getIntProp(prop);
+		return propValue == null ? defaultInt : propValue;
+	}
+	private Integer getIntProp(String prop) {
+		String v = configFile.getProperty(prop);
+		return v==null ? null : Integer.valueOf(v);
+	}
+	
+	@SuppressWarnings("unused")
+	private static Long getLongProperty(String prop, Long defaultLong) {
+		Long propValue = getModelConfig().getLongProp(prop);
+		return propValue == null ? defaultLong : propValue;
+	}
+	private Long getLongProp(String prop) {
+		String v = configFile.getProperty(prop);
+		return v==null ? null : Long.valueOf(v);
+	}
+	
+	private static Double getDoubleProperty(String prop, Double defaultDouble) {
+		Double propValue = getModelConfig().getDoubleProp(prop);
+		return propValue == null ? defaultDouble : propValue;
+	}
+	private Double getDoubleProp(String prop) {
+		String v = configFile.getProperty(prop);
+		return v==null ? null :  Double.valueOf(v);
+	}
+	
+	private static Boolean getBooleanProperty(String prop, Boolean defaultBoolean) {
+		return getModelConfig().getBooleanProp(prop, defaultBoolean);
+	}
+	private boolean getBooleanProp(String prop, Boolean defaultBoolean) {
+		String v = configFile.getProperty(prop);
+		return v==null ? defaultBoolean :  Boolean.valueOf(v);
+	}
+	
+	public static double updateParameterForShocks(int year, String parameter) {
+
+		Double value = null;
+		try {
+			value =  getModelConfig().getClass().getField(parameter).getDouble(parameter);
+
+			if(SHOCKS_POSSIBLE) {
+
+				if (shocksReader == null) {
+					shocksReader = new parameterShocksReader(",");
+					shocksReader.read(SHOCKS_PARAMETER_FILE);
+				}
+				Double updatedValue = shocksReader.queryForParameter(year, parameter);
+
+				if(updatedValue !=null) 
+					value = updatedValue;
+			}
+
+		}
+		catch (IllegalAccessException | NoSuchFieldException e) {
+			LogWriter.printlnError("cannot find parameter in model config to shock: " + parameter);
+			LogWriter.print(e);
+		}
+		return value;
+	}
+
+		
+	public static final boolean SUPPRESS_STD_OUTPUT = getBooleanProperty("SUPPRESS_STD_OUTPUT", Boolean.FALSE);
+
+	// Directory information
+	public static final String BASE_DIR = getProperty("BASE_DIR");  // this must to be set in config file
+    public static final String OUTPUT_DIR = getProperty("OUTPUT_DIR", ".");
+	public static final String TEMP_DIR = getProperty("TEMP_DIR", OUTPUT_DIR + File.separator + "GamsTmp");
+	public static final String DATA_DIR = getProperty("DATA_DIR", BASE_DIR + File.separator + "data");
+	public static final String GAMS_DIR = getProperty("GAMS_DIR", BASE_DIR + File.separator + "GAMS");
+	public static final boolean CLEANUP_GAMS_DIR = getBooleanProperty("CLEANUP_GAMS_DIR", false);
+
+	public static final boolean ORIG_LEAST_COST_MIN = getBooleanProperty("ORIG_LEAST_COST_MIN", true);
+	public static final String GAMS_MODEL_NAME = getProperty("GAMS_MODEL_NAME", ORIG_LEAST_COST_MIN==true ? "IntExtOpt.gms" : "LUOpt.gms");
+	public static final String GAMS_MODEL = getProperty("GAMS_MODEL", GAMS_DIR + File.separator + GAMS_MODEL_NAME);
+	public static final String DEMAND_GAMS_MODEL = getProperty("DEMAND_GAMS_MODEL", GAMS_DIR + File.separator + "elasticDemand.gms");
+	public static final String DEMAND_PARAM_FILE = getProperty("DEMAND_PARAM_FILE", DATA_DIR + File.separator + "DemandParamConv.gdx");
+
+	// Country (non-gridded) data
+	public static final boolean DEMAND_FROM_FILE = getBooleanProperty("DEMAND_FROM_FILE", false); // used in hindcasting
+	public static final boolean PRICE_ELASTIC_DEMAND = getBooleanProperty("PRICE_ELASTIC_DEMAND", true);
+	public static final boolean DONT_REBASE_DEMAND = getBooleanProperty("DONT_REBASE_DEMAND", false);;
+	public static final String DEMAND_CURVES_FILE = getProperty("DEMAND_CURVES_FILE", DATA_DIR + File.separator + "com_curves.csv");  // either DEMAND_CURVES_FILE or DEMAND_CONSUMPTION_FILE is used, but not both
+	public static final String DEMAND_CONSUMPTION_FILE = getProperty("DEMAND_CONSUMPTION_FILE", DATA_DIR + File.separator + "hist_comsump.csv");
+	public static final PriceType PRICE_CALCULATION = PriceType.findByName(getProperty("PRICE_CALCULATION", "weightedImportsExports"));
+	public static final String SSP_FILENAME = getProperty("SSP_FILENAME", "ssp.csv");
+	public static final String SSP_FILE = getProperty("SSP_FILE", DATA_DIR + File.separator + SSP_FILENAME);	
+	public static final String BASELINE_CONSUMP_FILE = DATA_DIR + File.separator + "base_consump.csv";
+	public static final String CALORIE_PER_T_FILE = DATA_DIR + File.separator + "calories_per_t.csv";
+	public static final String COUNTRY_CODES_FILE = DATA_DIR + File.separator + "country_codes4.csv";
+	public static final String COUNTRY_DATA_FILE = DATA_DIR + File.separator + "country_data.csv";
+	public static final String NET_IMPORTS_FILE = DATA_DIR + File.separator + "net_imports.csv";
+	public static final String BIOENERGY_1GEN_BASE_DEMAND_FILE = DATA_DIR + File.separator + "bio_demand.csv";
+	public static final String BIOENERGY_2GEN_DEMAND_FILENAME = getProperty("BIOENERGY_2GEN_DEMAND_FILENAME", "bioenergy_gen2_iiasa.csv");
+	public static final String BIOENERGY_2GEN_DEMAND_FILE = getProperty("BIOENERGY_2GEN_DEMAND_FILE", DATA_DIR + File.separator + BIOENERGY_2GEN_DEMAND_FILENAME);
+	public static final String BIOENERGY_1GEN_FUTURE_DEMAND_FILENAME = getProperty("BIOENERGY_FUTURE_DEMAND_FILENAME", "bioenergy_gen1.csv");
+	public static final String BIOENERGY_1GEN_FUTURE_DEMAND_FILE = getProperty("BIOENERGY_FUTURE_DEMAND_FILE", DATA_DIR + File.separator + BIOENERGY_1GEN_FUTURE_DEMAND_FILENAME);
+	public static final String TRADE_BARRIERS_FILENAME = getProperty("TRADE_BARRIERS_FILENAME", "tradeBarriers.csv");
+	public static final String TRADE_BARRIERS_FILE = getProperty("TRADE_BARRIERS_FILE", DATA_DIR + File.separator + TRADE_BARRIERS_FILENAME);
+	public static final String TRADE_DISTORTIONS_FILE = DATA_DIR + File.separator + "tradeDistortions.csv";
+	public static final String STOCKS_FILE = DATA_DIR + File.separator + "global_stocks.csv";
+	public static final String FAO_CONSUMPTION_FILE = DATA_DIR + File.separator + "fao_consump.csv";
+	public static final String COUNTRY_GROUPING_FILE = DATA_DIR + File.separator + "country_groups.csv";
+	public static final String OTHER_WATER_USES_FILE = DATA_DIR + File.separator + "other_water_uses.csv";
+	public static final String BASE_DEMAND_FRACT_FILE = DATA_DIR + File.separator + "base_demand_fracts.csv";
+	public static final String SHOCKS_PARAMETER_FILE = OUTPUT_DIR + File.separator+ "shocks.csv";
+	public static final String SUBSIDY_RATE_FILENAME = getProperty("SUBSIDY_RATE_FILENAME", "subsidyrates.csv");
+	public static final String SUBSIDY_RATE_FILE = getProperty("SUBSIDY_RATE_FILE", DATA_DIR + File.separator + SUBSIDY_RATE_FILENAME);
+	public static final String ANIMAL_RATES_FILE = DATA_DIR + File.separator + "animal_numbers.csv";;
+	public static final String INITIAL_CONSUMER_PRICE_FILE = DATA_DIR + File.separator + "consumerprices.csv";;
+	public static final String GDP_FRACTIONS_FILE = DATA_DIR + File.separator + "agriculturalGdpFraction.csv";
+
+	// yield data
+	public static final String YIELD_DIR_BASE = getProperty("YIELD_DIR_BASE");
+	public static final String YIELD_DIR_TOP = getProperty("YIELD_DIR_TOP");
+	public static final String YIELD_DIR = getProperty("YIELD_DIR", YIELD_DIR_BASE + File.separator + YIELD_DIR_TOP);
+	public static final int LPJG_MONITOR_TIMEOUT_SEC = getIntProperty("LPJG_MONITOR_TIMEOUT", 60*60*2);
+	public static final String ANPP_FILENAME = getProperty("ANPP_FILENAME", "anpp.out");
+	public static final String YIELD_FILENAME = getProperty("YIELD_FILENAME", "yield.out");
+	public static final boolean PASTURE_FERT_RESPONSE_FROM_LPJ = getBooleanProperty("PASTURE_FERT_RESPONSE_FROM_LPJ", false);;
+
+	public static final double CALIB_FACTOR_CEREAL_C3 = getDoubleProperty("CALIB_FACTOR_CEREAL_C3", 1.046);
+	public static final double CALIB_FACTOR_CEREAL_C4 = getDoubleProperty("CALIB_FACTOR_CEREAL_C4", 0.654);
+	public static final double CALIB_FACTOR_MISCANTHUS = getDoubleProperty("CALIB_FACTOR_MISCANTHUS", 2.148);
+	public static final double CALIB_FACTOR_RICE = getDoubleProperty("CALIB_FACTOR_RICE", 0.972);
+	public static final double CALIB_FACTOR_OILCROPS = getDoubleProperty("CALIB_FACTOR_OILCROPS", 0.578);
+	public static final double CALIB_FACTOR_PULSES = getDoubleProperty("CALIB_FACTOR_PULSES", 0.686);
+	public static final double CALIB_FACTOR_STARCHY_ROOTS = getDoubleProperty("CALIB_FACTOR_STARCHY_ROOTS",4.560);
+	public static final double CALIB_FACTOR_FRUITVEG = getDoubleProperty("CALIB_FACTOR_FRUITVEG",3.526);
+	public static final double CALIB_FACTOR_SUGAR = getDoubleProperty("CALIB_FACTOR_SUGAR", 11.909);
+
+	// These are production prices in PLUM style feed equivalent terms
+	public static final double INITIAL_PRICE_SHIFT = getDoubleProperty("INITIAL_PRICE_SHIFT", 1.0);
+	public static final double INITAL_PRICE_WHEAT = getDoubleProperty("INITAL_PRICE_WHEAT", 0.157 * ModelConfig.INITIAL_PRICE_SHIFT);
+	public static final double INITAL_PRICE_MAIZE = getDoubleProperty("INITAL_PRICE_MAIZE", 0.152 * ModelConfig.INITIAL_PRICE_SHIFT);
+	public static final double INITAL_PRICE_RICE = getDoubleProperty("INITAL_PRICE_RICE", 0.182 * ModelConfig.INITIAL_PRICE_SHIFT);
+	public static final double INITAL_PRICE_OILCROPS = getDoubleProperty("INITAL_PRICE_OILCROPS", (0.820 * .4 + 0.314 * .6) * 0.3 * ModelConfig.INITIAL_PRICE_SHIFT);
+	public static final double INITAL_PRICE_PULSES = getDoubleProperty("INITAL_PRICE_PULSES", 0.2 * ModelConfig.INITIAL_PRICE_SHIFT);
+	public static final double INITAL_PRICE_STARCHYROOTS = getDoubleProperty("INITAL_PRICE_STARCHYROOTS", 0.1 * ModelConfig.INITIAL_PRICE_SHIFT);
+	public static final double INITAL_PRICE_MONOGASTRICS = getDoubleProperty("INITAL_PRICE_MONOGASTRICS", 0.4 * 0.5 * ModelConfig.INITIAL_PRICE_SHIFT); // quantities is in feed equivalent term (0.4 is weighted average price per feed, and 0.5 accounts for mark-up for additional processing)
+	public static final double INITAL_PRICE_RUMINANTS = getDoubleProperty("INITAL_PRICE_RUMINANTS", 0.1 * 0.6 * ModelConfig.INITIAL_PRICE_SHIFT); // quantities is in feed equivalent term
+	public static final double INITAL_PRICE_ENERGYCROPS = getDoubleProperty("INITAL_PRICE_ENERGYCROPS", 0.04 * ModelConfig.INITIAL_PRICE_SHIFT);
+	public static final double INITAL_PRICE_FRUITVEG = getDoubleProperty("INITAL_PRICE_FRUITVEG", 0.1 * ModelConfig.INITIAL_PRICE_SHIFT);
+	public static final double INITAL_PRICE_SUGAR = getDoubleProperty("INITAL_PRICE_SUGAR", 0.02 * ModelConfig.INITIAL_PRICE_SHIFT);
+
+	// These are initial demand system prices in 2000 kcal terms
+	public static final double INITAL_DEMAND_PRICE_CEREALS = getDoubleProperty("INITAL_DEMAND_PRICE_CEREALS", 120.2365);
+	public static final double INITAL_DEMAND_PRICE_OILCROPS_PULSES = getDoubleProperty("INITAL_DEMAND_PRICE_OILCROPS_PULSES", 147.4032);
+	public static final double INITAL_DEMAND_PRICE_STARCHYROOTS = getDoubleProperty("INITAL_DEMAND_PRICE_STARCHYROOTS", 543.5512);
+	public static final double INITAL_DEMAND_PRICE_MONOGASTRICS = getDoubleProperty("INITAL_DEMAND_PRICE_MONOGASTRICS", 1243.899);
+	public static final double INITAL_DEMAND_PRICE_RUMINANTS = getDoubleProperty("INITAL_DEMAND_PRICE_RUMINANTS", 1043.901);
+	public static final double INITAL_DEMAND_PRICE_FRUITVEG = getDoubleProperty("INITAL_DEMAND_PRICE_FRUITVEG", 3381.178);
+	public static final double INITAL_DEMAND_PRICE_SUGAR = getDoubleProperty("INITAL_DEMAND_PRICE_SUGAR", 168.9247);
+
+	// Spatial (gridded) data
+	public static final double CELL_SIZE_X = getDoubleProperty("CELL_SIZE_X", 0.5);
+	public static final double CELL_SIZE_Y = getDoubleProperty("CELL_SIZE_Y", CELL_SIZE_X);
+	public static final String SPATIAL_DIR_NAME = getProperty("SPATIAL_DIR_NAME", "halfdeg");
+	public static final String SPATIAL_DATA_DIR = getProperty("SPATIAL_DATA_DIR", DATA_DIR + File.separator + SPATIAL_DIR_NAME);
+	public static final String INITAL_LAND_COVER_FILENAME = getProperty("INITAL_LAND_COVER_FILENAME", "hurtt_2010.txt");
+	public static final String INITAL_LAND_COVER_FILE = SPATIAL_DATA_DIR + File.separator + INITAL_LAND_COVER_FILENAME;
+	public static final String COUNTRY_BOUNDARY_FILE = SPATIAL_DATA_DIR + File.separator + "country_boundaries.asc";
+	public static final String IRRIGATION_COST_FILE = SPATIAL_DATA_DIR + File.separator + "irrigation_cost.asc";
+	public static final String IRRIGATION_CONSTRAINT_FILE = SPATIAL_DATA_DIR + File.separator + "blue_water_available_pseudoCRU_rcp8p5_2004_2013_grid_allhdyro_mm.txt";
+	public static final String FPU_BOUNDARIES_FILE = SPATIAL_DATA_DIR + File.separator + "FPU.asc";
+	public static final String FPU_GROUPING_FILE = SPATIAL_DATA_DIR + File.separator + "fpuGrouping.txt";
+	public static final String IRRIG_MAX_WATER_FILENAME = getProperty("IRRIG_MAX_WATER_FILENAME", "gsirrigation.out");
+	public static final String IRRIG_RUNOFF_FILE = getProperty("IRRIG_RUNOFF_FILE", "tot_runoff.out");
+	public static final String PROTECTED_AREAS_FILE = getProperty("PROTECTED_AREAS_FILE",SPATIAL_DATA_DIR + File.separator + "protectedAreas2019.asc");
+	public static final String HALF_EARTH_FILE = getProperty("HALF_EARTH_FILE",SPATIAL_DATA_DIR + File.separator + "global_biodiversity_priorities50perc.asc");
+	public static final String HIGH_SLOPE_AREAS_FILE = SPATIAL_DATA_DIR + File.separator + "maxcropfrac2.txt";
+	public static final String YIELDSHOCK_MAP_DIR = SPATIAL_DATA_DIR + File.separator + "yieldshockmaps";
+	public static final String YIELDSHOCKS_PARAMETER_FILE = getProperty("YIELDSHOCKS_PARAMETER_FILE", OUTPUT_DIR + File.separator+ "yieldshocks.csv");
+	public static final String PRICESHOCKS_PARAMETER_FILE = getProperty("PRICESHOCKS_PARAMETER_FILE", OUTPUT_DIR + File.separator+ "priceshocks.csv");
+	public static final String EXPORT_RESTRICTIONS_FILE = getProperty("EXPORT_RESTRICTIONS_FILE", OUTPUT_DIR + File.separator+ "exportrestictions.csv");
+	
+	// Wood/carbon data
+	public static final String FOREST_DIR = SPATIAL_DATA_DIR + File.separator + "forestry";
+	public static final String WOOD_AND_CARBON_DIR = getProperty("WOOD_AND_CARBON_DIR");
+	//public static final String CARBON_FLUX_FILE = FOREST_DIR + File.separator + "carbon_flux_";
+	public static final String WOOD_YIELD_FRST_TO_AGRI_FILENAME = "wood_yield_forest_to_agri.dat";
+	public static final String WOOD_YIELD_FRST_TO_FRST_FILENAME = "wood_yield_forest_to_forest.dat";
+	public static final String WOOD_YIELD_NTRL_TO_AGRI_FILENAME = "wood_yield_ntrl_to_agri.dat";
+	public static final String WOOD_YIELD_NTRL_TO_FRST_FILENAME = "wood_yield_ntrl_to_forest.dat";
+	public static final String CARBON_LUC_FILENAME = "carbon_luc.out";
+	public static final String CARBON_NEE_FILENAME = "carbon_nee.out";
+	public static final String NATURAL_FOREST_GROWTH_FILENAME = "growth_natural.out";
+	public static final String LAND_COVER_AGE_DIST_FILENAME = SPATIAL_DATA_DIR + File.separator + "land_cover_age_dist.txt";
+	public static final int LAND_COVER_INIT_AGE_GROUP_SIZE = getIntProperty("CARBON_WOOD_AGE_CLASSES", 10); // years
+	public static final int WOOD_AND_CARBON_TIMESTEP_SIZE = getIntProperty("WOOD_AND_CARBON_TIMESTEP_SIZE", 20); // years
+	public static final double WOOD_YIELD_CALIB_FACTOR = getDoubleProperty("WOOD_YIELD_CALIB_FACTOR", 6.0); 
+	public static final int CARBON_WOOD_MAX_TIME = getIntProperty("CARBON_WOOD_AGE_CLASSES", 165);
+
+	// Output
+	public static final String LAND_COVER_OUTPUT_FILE = OUTPUT_DIR + File.separator + "lc.txt";
+	public static final String PRICES_OUTPUT_FILE = OUTPUT_DIR + File.separator + "prices.txt";
+	public static final String DEMAND_OUTPUT_FILE = OUTPUT_DIR + File.separator + "demand.txt";
+	public static final String DOMESTIC_OUTPUT_FILE = OUTPUT_DIR + File.separator + "domestic.txt";
+	public static final String COUNTRY_DEMAND_FILE = OUTPUT_DIR + File.separator + "countryDemand.txt";
+	public static final String DEMAND_OPTIMISATION_OUTPUT_FILE = OUTPUT_DIR + File.separator + "countryDemandOpt.txt";
+	public static final String FOOD_BALANCE_SHEET_FILE = OUTPUT_DIR + File.separator + "fbs.txt";
+	public static final String ANIMAL_NUMBERS_OUTPUT_FILE = OUTPUT_DIR + File.separator + "animals.txt";
+	public static final String WOOD_CARBON_OUTPUT_FILE = OUTPUT_DIR + File.separator + "woodCarbon.txt";
+
+	public static final boolean OUTPUT_FOR_LPJG = getBooleanProperty("OUTPUT_FOR_LPJG", true);
+	public static final boolean INTERPOLATE_OUTPUT_YEARS = getBooleanProperty("INTERPOLATE_OUTPUT_YEARS", true);	
+	
+	// Calibration related stuff
+	public static final boolean IS_CALIBRATION_RUN = getBooleanProperty("IS_CALIBRATION_RUN", false);
+	public static final String CALIB_DIR = IS_CALIBRATION_RUN ? OUTPUT_DIR : getProperty("CALIB_DIR", OUTPUT_DIR);
+	public static final int END_FIRST_STAGE_CALIBRATION = getIntProperty("END_FIRST_STAGE_CALIBRATION", 10);
+	public static final String SERIALIZED_LAND_USE_FILENAME = "landUseRaster.ser";
+	public static final String SERIALIZED_CROP_USAGE_FILENAME = "countryCropUsages.ser";
+	public static final String SERIALIZED_INTERNATIONAL_MARKET_FILENAME = "internationalMarket.ser";
+	public static final String SERIALIZED_WOOD_USAGE_FILENAME = "countryWoodUsage.ser";
+	public static final String SERIALIZED_LAND_COVER_FILENAME = "landCoverData.dat";
+	public static final String SERIALIZED_LAND_USE_FILE = CALIB_DIR + File.separator +  SERIALIZED_LAND_USE_FILENAME;
+	public static final String SERIALIZED_CROP_USAGE_FILE = CALIB_DIR + File.separator +  SERIALIZED_CROP_USAGE_FILENAME;
+	public static final String SERIALIZED_INTERNATIONAL_MARKET_FILE = CALIB_DIR + File.separator +  SERIALIZED_INTERNATIONAL_MARKET_FILENAME;
+	public static final String SERIALIZED_LAND_COVER_FILE = CALIB_DIR + File.separator + SERIALIZED_LAND_COVER_FILENAME;
+	public static final String SERIALIZED_WOOD_USAGE_FILE = CALIB_DIR + File.separator + SERIALIZED_WOOD_USAGE_FILENAME;
+	public static final String CHECKPOINT_LAND_USE_FILE = getProperty("CHECKPOINT_LAND_USE_FILE", OUTPUT_DIR + File.separator +  SERIALIZED_LAND_USE_FILENAME);
+	public static final String CHECKPOINT_CROP_USAGE_FILE = getProperty("CHECKPOINT_CROP_USAGE_FILE", OUTPUT_DIR + File.separator +  SERIALIZED_CROP_USAGE_FILENAME);
+	public static final String CHECKPOINT_INTERNATIONAL_MARKET_FILE = getProperty("CHECKPOINT_INTERNATIONAL_MARKET_FILE", OUTPUT_DIR + File.separator +  SERIALIZED_INTERNATIONAL_MARKET_FILENAME);
+	public static final String CHECKPOINT_LAND_COVER_FILE = getProperty("CHECKPOINT_LAND_COVER_FILE", OUTPUT_DIR + File.separator +  SERIALIZED_LAND_COVER_FILENAME);
+	public static final String CHECKPOINT_WOOD_USAGE_FILE = getProperty("CHECKPOINT_WOOD_USAGE_FILE", OUTPUT_DIR + File.separator +  SERIALIZED_WOOD_USAGE_FILENAME);
+	
+	public static final boolean MARKET_ADJ_PRICE = getBooleanProperty("MARKET_ADJ_PRICE", true);
+	public static final boolean CHANGE_YIELD_DATA_YEAR = IS_CALIBRATION_RUN ? false : getBooleanProperty("CHANGE_YIELD_DATA_YEAR", true);
+	public static final String CLUSTERED_YIELD_FILE = getProperty("CLUSTERED_YIELD_FILE", CALIB_DIR + File.separator + "cluster.asc");
+	public static final boolean GENERATE_NEW_YIELD_CLUSTERS = getBooleanProperty("GENERATE_NEW_YIELD_CLUSTERS", IS_CALIBRATION_RUN);
+	
+
+	// Temporal configuration
+	public static final int START_TIMESTEP = getIntProperty("START_TIMESTEP", 0);
+	public static final int END_TIMESTEP = getIntProperty("END_TIMESTEP", 90);
+	public static final int TIMESTEP_SIZE = getIntProperty("TIMESTEP_SIZE", 1);
+	public static final int BASE_YEAR = getIntProperty("BASE_YEAR", 2010);
+
+	// Import export limits
+	public static final double ANNUAL_MAX_IMPORT_CHANGE = getDoubleProperty("ANNUAL_MAX_IMPORT_CHANGE", 0.05);
+	public static final double MAX_IMPORT_CHANGE = getDoubleProperty("MAX_IMPORT_CHANGE", ANNUAL_MAX_IMPORT_CHANGE*TIMESTEP_SIZE);
+
+	// Fertiliser application rates in kg/ha
+	public static final double MIN_FERT_AMOUNT = getDoubleProperty("MIN_FERT_AMOUNT", 0.0);
+	public static final double MID_FERT_AMOUNT = getDoubleProperty("MID_FERT_AMOUNT", 200.0);
+	public static final double MAX_FERT_AMOUNT = getDoubleProperty("MAX_FERT_AMOUNT", 1000.0);
+	public static final int FERT_AMOUNT_PADDING = getIntProperty("FERT_AMOUNT_PADDING", 4);;
+
+	// SSP shift parameters
+	public static final double SSP_POPULATION_FACTOR = getDoubleProperty("SSP_POPULATION_FACTOR", 1.0);
+	public static final double SSP_GDP_PC_FACTOR = getDoubleProperty("SSP_GDP_PC_FACTOR", 1.0);
+
+	// Other model parameters
+	public static final boolean CHANGE_DEMAND_YEAR = IS_CALIBRATION_RUN ? false : getBooleanProperty("CHANGE_DEMAND_YEAR", true);
+	public static final double DIETARY_CLOSURE = getDoubleProperty("DIETARY_CLOSURE", 0.25); // Amount diet converges in DIETARY_CLOSURE_GDP_CHANGE rate of GDP shift
+	public static final double DIETARY_CLOSURE_GDP_CHANGE = getDoubleProperty("DIETARY_CLOSURE_GDP_CHANGE", 2.0); // 2 is double of GDP
+	public static final double DIETARY_CLOSURE_PARAM = getDoubleProperty("DIETARY_CLOSURE_PARAM", Math.log((1-DIETARY_CLOSURE))/DIETARY_CLOSURE_GDP_CHANGE); // Zero is no dietary closure, number specifies closure rate for changes in GDP per capita, e.g. Math.log(0.5)/2
+	public static final String SSP_SCENARIO = getProperty("SSP_SCENARIO", "SSP1_v9_130325");
+	public static final ModelFitType DEMAND_ANIMAL_PROD_FIT = ModelFitType.findByName(getProperty("DEMAND_ANIMAL_PROD_FIT", "loglinear"));
+	public static final ModelFitType DEMAND_NON_ANIMAL_PROD_FIT = ModelFitType.findByName(getProperty("DEMAND_NON_ANIMAL_PROD_FIT", "loglinear"));
+	public static final boolean LIMIT_DEMAND_FRACTION = getBooleanProperty("LIMIT_DEMAND_FRACTION", true);
+	public static final boolean DEMAND_FRACT_BY_COST = getBooleanProperty("DEMAND_FRACT_BY_COST", false);;
+	public static final double MAX_INCOME_PROPORTION_FOOD_SPEND = getDoubleProperty("MAX_INCOME_PROPORTION_FOOD_SPEND", 0.7);
+	
+	public static final double PASTURE_HARVEST_FRACTION = getDoubleProperty("PASTURE_HARVEST_FRACTION", 0.5);
+	public static final double MEAT_EFFICIENCY = getDoubleProperty("MEAT_EFFICIENCY", 1.0);  // 'meat' is includes feed conversion ratio already, this is tech. change or similar
+	public static final double IRRIGIATION_EFFICIENCY = getDoubleProperty("IRRIGIATION_EFFICIENCY", 0.5);
+	public static final int ELLIOTT_BASEYEAR = 2010;
+	public static final double ENVIRONMENTAL_WATER_CONSTRAINT = getDoubleProperty("ENVIRONMENTAL_WATER_CONSTRAINT", 0.5); // change with care, as due to normalisation it might not have the impact you first imagine
+	public static final double OTHER_WATER_USE_FACTOR = getDoubleProperty("OTHER_WATER_USE_FACTOR", 1.0);
+	public static final boolean USE_BLUE_WATER_FILE_IRRIG_CONSTRAINT = getBooleanProperty("USE_BLUE_WATER_FILE_IRRIG_CONSTRAINT", false);;
+
+	public static final double LAND_CHANGE_COST = getDoubleProperty("LAND_CHANGE_COST", 0.2);
+	public static final double CROP_TO_PASTURE_COST_FACTOR = getDoubleProperty("CROP_TO_PASTURE_COST_FACTOR", 1.0);
+	public static final double AGRI_LAND_EXPANSION_COST_FACTOR = getDoubleProperty("AGRI_LAND_EXPANSION_COST_FACTOR", 1.0);
+
+	public static final double CROP_INCREASE_COST = getDoubleProperty("CROP_INCREASE_COST", 0.05 * LAND_CHANGE_COST * AGRI_LAND_EXPANSION_COST_FACTOR);
+	public static final double PASTURE_DECREASE_COST = getDoubleProperty("PASTURE_DECREASE_COST", LAND_CHANGE_COST);
+	public static final double CROP_DECREASE_COST = getDoubleProperty("CROP_DECREASE_COST", 1.65 * LAND_CHANGE_COST);
+	public static final double PASTURE_INCREASE_COST = getDoubleProperty("PASTURE_INCREASE_COST", 0.35 * LAND_CHANGE_COST * CROP_TO_PASTURE_COST_FACTOR * AGRI_LAND_EXPANSION_COST_FACTOR);
+	public static final double AGRI_EXPANSION_COST_BASE = getDoubleProperty("AGRI_EXPANSION_COST_BASE", 0.03 * LAND_CHANGE_COST * AGRI_LAND_EXPANSION_COST_FACTOR);
+	public static final double MANAGED_FOREST_INCREASE_COST = getDoubleProperty("MANAGED_FOREST_INCREASE_COST", 0.5 * LAND_CHANGE_COST); // $1000/ha
+	public static final double MANAGED_FOREST_DECREASE_COST = getDoubleProperty("MANAGED_FOREST_DECREASE_COST", 0.5 * LAND_CHANGE_COST); // $1000/ha
+
+	public static final double TECHNOLOGY_CHANGE_ANNUAL_RATE = getDoubleProperty("TECHNOLOGY_CHANGE_ANNUAL_RATE", 0.002);
+	public static final int TECHNOLOGY_CHANGE_START_STEP = getIntProperty("TECHNOLOGY_CHANGE_START_STEP", 0);
+
+	public static final boolean USE_BIOENERGY_TRAJECTORY = getBooleanProperty("USE_BIOENERGY_TRAJECTORY", true);  // false is the old style, i.e. BIOENERGY_CHANGE_ANNUAL_RATE, BIOENERGY_CHANGE_START_YEAR and BIOENERGY_CHANGE_END_YEAR
+	// original way to specify changes in bioenergy
+	public static final double BIOENERGY_CHANGE_ANNUAL_RATE = IS_CALIBRATION_RUN ? 0.0 : getDoubleProperty("BIOENERGY_CHANGE_ANNUAL_RATE", 0.10); // 6.2/2.31/20
+	public static final int BIOENERGY_CHANGE_START_YEAR = getIntProperty("BIOENERGY_CHANGE_START_YEAR", 2010);
+	public static final int BIOENERGY_CHANGE_END_YEAR = getIntProperty("BIOENERGY_CHANGE_END_YEAR", 2060);
+	// newer way
+	public static final boolean ENABLE_GEN2_BIOENERGY = getBooleanProperty("ENABLE_GEN2_BIOENERGY", true);
+	public static final String BIOENERGY_DEMAND_SCENARIO = getProperty("BIOENERGY_DEMAND_SCENARIO", "SSP2_RCP45");
+	public static final String GEN2_BIOENERGY_MODEL = getProperty("GEN2_BIOENERGY_MODEL", "ENSEMBLE-MEAN");
+	public static final double BIOENERGY_DEMAND_SHIFT = IS_CALIBRATION_RUN ? 1.0 : getDoubleProperty("BIOENERGY_DEMAND_SHIFT", 1.0);
+//	public static final double BIOENERGY_HEATING_VALUE_GJ_PER_T = getDoubleProperty("BIOENERGY_HEATING_VALUE_GJ_PER_T", 17.5); // GJ per t DM
+	
+	//Dietary stuff
+	public static final double RUMINANT_CHANGE_ANNUAL_RATE  = getDoubleProperty("RUMINANT_CHANGE_ANNUAL_RATE", 0.0);
+	public static final double MONOGASTRIC_CHANGE_ANNUAL_RATE  = getDoubleProperty("MONOGASTRIC_CHANGE_ANNUAL_RATE", 0.00);
+	public static final boolean CONSTANT_DIET_LOW_INCOME = getBooleanProperty("CONSTANT_DIET_LOW_INCOME", false);
+	public static final boolean CONSTANT_DIET_HIGH_INCOME  = getBooleanProperty("CONSTANT_DIET_HIGH_INCOME", false);
+	//below should all sum to zero
+	public static final double CEREALS_SUB_PROPORTION  = getDoubleProperty("CEREALS_SUB_PROPORTION", 0.3);
+	public static final double OILCROPSPULSES_SUB_PROPORTION  = getDoubleProperty("OILCROPSPULSES_SUB_PROPORTION", 0.25);
+	public static final double STARCHY_ROOTS_SUB_PROPORTION  = getDoubleProperty("STARCHY_ROOTS_SUB_PROPORTION", 0.25);
+	public static final double FRUITVEG_SUB_PROPORTION  = getDoubleProperty("FRUITVEG_SUB_PROPORTION", 0.1);
+	public static final double SUGAR_SUB_PROPORTION  = getDoubleProperty("SUGAR_SUB_PROPORTION", 0.1);
+
+	public static final double MARKET_LAMBA = getDoubleProperty("MARKET_LAMBA", 0.4); // controls international market price adjustment rate
+	public static final boolean PRICE_UPDATE_BY_MARKET_IMBALANCE = getBooleanProperty("PRICE_UPDATE_BY_MARKET_IMBALANCE", false);;
+	public static final double MAX_PRICE_INCREASE = getDoubleProperty("MAX_PRICE_INCREASE", 1.5);
+	public static final double MAX_PRICE_DECREASE = getDoubleProperty("MAX_PRICE_DECREASE", .75);
+	public static final int DEMAND_RECALC_MAX_ITERATIONS = IS_CALIBRATION_RUN ? 0 : getIntProperty("DEMAND_RECALC_MAX_ITERATIONS", 1);  // 0 is original behaviour
+	public static final boolean DEMAND_RECALC_ON_NEGATIVE_STOCK = IS_CALIBRATION_RUN ? false : getBooleanProperty("DEMAND_RECALC_ON_NEGATIVE_STOCK", false); 
+
+	public static final double POPULATION_AGGREG_LIMIT = getDoubleProperty("POPULATION_AGGREG_LIMIT", 30.0);  // in millions, smaller countries are aggregated on a regional basis
+	public static final boolean PREDEFINED_COUNTRY_GROUPING = getBooleanProperty("PREDEFINED_COUNTRY_GROUPING", true);
+
+	// 1536.7 Mha is all cropland in LUH2, or 1555.6 Mha from FAO.  country_data[Country =="World" & Year == 2011, list(arable+ perm_crops)]
+	// 1377.6 Mha is area of crops in FAO crop production.  con_prod_unadj[Country =="World" & Item %in% itemDetails[produced==TRUE]$Item & Year == 2011, sum(prod_area, na.rm=TRUE)]
+	// 1105.6 Mha is harvested in crops we represent.  crop_prod[Country =="World" & Item %in% itemGroupMapping$cropProdItem & Year == 2011, sum(area)]
+	// extract values by running AltLURead.R and common.R 
+	// So we do not represent crops covering (1341.911-1239.03)/1547.464=6.65% of cropland.  Additionally 10.3% of cropland is set aside, fallow or failed crops, 
+	public static final double UNHANDLED_CROP_RATE = getDoubleProperty("UNHANDLED_CROP_RATE", 0.0665);  // mostly forage crops
+	public static final double SETASIDE_RATE = getDoubleProperty("SETASIDE_RATE", 0.103);  // includes aside, fallow and failed cropland areas 
+	
+	public static final double OTHER_INTENSITY_COST = getDoubleProperty("OTHER_INTENSITY_COST", 0.8);
+	public static final double OTHER_INTENSITY_PARAM = getDoubleProperty("OTHER_INTENSITY_PARAM", 3.22);
+
+	public static final double IRRIG_COST_SCALE_FACTOR = getDoubleProperty("IRRIG_COST_SCALE_FACTOR", 0.0003);
+	public static final double IRRIG_COST_MULTIPLIER = getDoubleProperty("IRRIG_COST_MULTIPLIER", 1.0);
+	public static final double FERTILISER_COST_PER_T = getDoubleProperty("FERTILISER_COST_PER_T", 1.4); // $500/t, 18% N/t
+	public static final double FERTILISER_MAX_COST = FERTILISER_COST_PER_T * MAX_FERT_AMOUNT/1000;
+	
+	public static final double DOMESTIC_PRICE_MARKUP = getDoubleProperty("DOMESTIC_PRICE_MARKUP", 1.0);
+	public static final double TRANSPORT_LOSSES = getDoubleProperty("TRANSPORT_LOSSES", 0.05);  // in international trade
+	public static final double TRANSPORT_COST = getDoubleProperty("TRANSPORT_COST", 0.1);  // 10% transport cost
+	public static final double TRADE_BARRIER_FACTOR_DEFAULT = getDoubleProperty("TRADE_BARRIER_FACTOR_DEFAULT", 0.2);  // price factor in international trade, transport cost and real trade barriers
+	public static final double TRADE_BARRIER_MULTIPLIER = getDoubleProperty("TRADE_BARRIER_MULTIPLIER", 1.0);
+	public static final boolean ACTIVE_TRADE_BARRIERS = getBooleanProperty("ACTIVE_TRADE_BARRIERS", true);  // if set to true read in barrier information from file, otherwise use default as above
+	public static final double TRADE_BARRIER_ENERGY_CROPS = getDoubleProperty("TRADE_BARRIER_ENERGY_CROPS", 0.01);  // price factor in international trade, transport cost and real trade barriers
+	public static final boolean SHOCKS_POSSIBLE = getBooleanProperty("SHOCKS_POSSIBLE", false);
+	public static final double YIELD_SHOCK_MAGNIFIER = getDoubleProperty("YIELD_SHOCK_MAGNIFIER", 1.0);
+	
+	public static final boolean PROTECTED_AREAS_ENABLED = getBooleanProperty("PROTECTED_AREAS_ENABLED", true);
+	public static final double MIN_NATURAL_RATE = getDoubleProperty("MIN_NATURAL_RATE", 0.05);
+	public static final double MAX_CHINA_LAND_EXPANSION_RATE = getDoubleProperty("MAX_CHINA_LAND_EXPANSION_RATE", 0.011*0.4); // 1.1% max forest change 40% of natural land is forest
+
+	public static final boolean DEBUG_JUST_DEMAND_OUTPUT = getBooleanProperty("DEBUG_JUST_DEMAND_OUTPUT", false);
+	public static final boolean DEBUG_LIMIT_COUNTRIES = getBooleanProperty("DEBUG_LIMIT_COUNTRIES", false);
+	public static final String DEBUG_COUNTRY_NAME = getProperty("DEBUG_COUNTRY_NAME", "United States of America");
+	public static final String GAMS_COUNTRY_TO_SAVE = getProperty("GAMS_COUNTRY_TO_SAVE", "China");;
+	public static final boolean EXCLUDE_COUNTRIES_IN_LIST = getBooleanProperty("EXCLUDE_COUNTRIES_IN_LIST", false);
+	public static final String EXCLUDED_COUNTRIES_FILE = DATA_DIR + File.separator + "countries_excluded.csv";
+	
+	public static final double PASTURE_MAX_IRRIGATION_RATE = getDoubleProperty("DEFAULT_MAX_IRRIGATION_RATE", 50.0); // shouldn't need this but some areas crops don't have a value, but was causing them to be selected
+	public static final int LPJG_TIMESTEP_SIZE = getIntProperty("LPJG_TIMESTEP_SIZE", 5);
+	public static final int LPJ_YEAR_OFFSET = getIntProperty("LPJ_YEAR_OFFSET", 0);;
+	
+	public static final int NUM_YIELD_CLUSTERS = getIntProperty("NUM_YIELD_CLUSTERS", 8000);
+	public static final long RANDOM_SEED = getIntProperty("RANDOM_SEED", 1974329);  // any number will do
+	
+	public static final String CHECKPOINT_YEARS = getProperty("CHECKPOINT_YEARS", null); // 2020,2050
+	
+	// Protected areas forcing parameters
+	public static final boolean FORCE_PROTECTED_AREAS = IS_CALIBRATION_RUN ? false : getBooleanProperty("FORCE_PROTECTED_AREAS", false);
+	public static final int FORCE_PROTECTED_AREAS_START_YEAR  = getIntProperty("FORCE_PROTECTED_AREAS_START_YEAR", 2020);
+	public static final int FORCE_PROTECTED_AREAS_END_YEAR  = getIntProperty("FORCE_PROTECTED_AREAS_END_YEAR", 2040);
+	public static final double CONSTANT_PROTECTED_AREA_RATE = getDoubleProperty("CONSTANT_PROTECTED_AREA_RATE", Double.NaN);
+	public static final boolean HALFEARTH =  getBooleanProperty("HALFEARTH", false);
+
+	public static final boolean USE_CRAFTY_COUNTRIES = getBooleanProperty("USE_CRAFTY_COUNTRIES", false);
+	public static final String CRAFTY_PRODUCTION_DIR = getProperty("CRAFTY_PRODUCTION_DIR", OUTPUT_DIR + File.separator + "crafty");
+	
+	public static final boolean EXTRAPOLATE_YIELD_FERT_RESPONSE = getBooleanProperty("EXTRAPOLATE_YIELD_FERT_RESPONSE", false);
+	
+	public static final boolean ADJUST_DIET_PREFS = getBooleanProperty("ADJUST_DIET_PREFS", false);
+	public static final int DIET_CHANGE_START_YEAR = getIntProperty("DIET_CHANGE_START_YEAR", 2020);
+	public static final int DIET_CHANGE_END_YEAR = getIntProperty("DIET_CHANGE_END_YEAR", 2040);
+	public static final String TARGET_DIET_FILE = getProperty("TARGET_DIET_FILE", DATA_DIR + File.separator + "TargetDiet.txt");
+	public static final boolean APPLY_EXPORT_TAXES = getBooleanProperty("APPLY_EXPORT_TAXES", false);
+	public static final double EXPORT_TAX_RATE = getDoubleProperty("EXPORT_TAX_RATE", 1.0);
+	public static final double EXPORT_TAX_THRESHOLD = getDoubleProperty("EXPORT_TAX_THRESHOLD", 0.1);
+	
+	// Forestry and carbon market parameters
+	public static final String CONVERSION_COST_FILE = DATA_DIR + File.separator + "conversion_costs.csv"; // cost of converting from one land cover to another, $1000/ha
+	public static final String CARBON_DEMAND_FILENAME = getProperty("CARBON_DEMAND_FILENAME", "carbon_demand.csv");
+	public static final String CARBON_DEMAND_FILE = getProperty("CARBON_DEMAND_FILE", DATA_DIR + File.separator + CARBON_DEMAND_FILENAME);
+	public static final String TIMBER_DEMAND_FILENAME = getProperty("TIMBER_DEMAND_FILENAME", "timber_demand.csv");
+	public static final String TIMBER_DEMAND_FILE = getProperty("TIMBER_DEMAND_FILE", DATA_DIR + File.separator + TIMBER_DEMAND_FILENAME);
+
+	public static final double INIT_WOOD_STOCK = getDoubleProperty("INIT_WOOD_STOCK", 1000.0); // tC-eq
+	public static final double ROUNDWOOD_DEMAND_ELASTICITY = getDoubleProperty("ROUNDWOOD_DEMAND_ELASTICITY", -0.21956);
+	public static final double FUELWOOD_DEMAND_ELASTICITY = getDoubleProperty("FUELDWOOD_DEMAND_ELASTICITY", -0.22787);
+	public static final double FOREST_ESTABLISHMENT_COST = getDoubleProperty("FOREST_ESTABLISHMENT_COST", 2.0); // $1000/ha
+	public static final String WOOD_NET_IMPORTS_FILENAME = getProperty("WOOD_NET_IMPORTS_FILENAME", "wood_net_imports.csv");
+	public static final String WOOD_NET_IMPORTS_FILE = getProperty("WOOD_NET_IMPORTS_FILE", DATA_DIR + File.separator + WOOD_NET_IMPORTS_FILENAME);
+	public static final double WOOD_BIOMASS_CONVERSION_FACTOR = getDoubleProperty("WOOD_BIOMASS_CONVERSION_FACTOR", 3e-7); // m3 to MtC-eq p.16 [https://doi.org/10.5194/gmd-13-5425-2020] 
+	public static final double DISCOUNT_RATE = getDoubleProperty("DISCOUNT_RATE", 0.07);
+	public static final double VEGETATION_CLEARING_COST = getDoubleProperty("VEGETATION_CLEARING_COST", 0.005); //$1000/tC
+	public static final boolean CONVERSION_COSTS_FROM_FILE = getBooleanProperty("CONVERSION_COSTS_FROM_FILE", false);
+	public static final boolean IS_FORESTRY_ON = getBooleanProperty("IS_FORESTRY_ON", true);
+	public static final boolean IS_CARBON_ON = getBooleanProperty("IS_CARBON_ON", true);
+	public static final int CARBON_HORIZON = getIntProperty("CARBON_HORIZON", 30);
+	public static final double WOOD_TRADE_BARRIER = getDoubleProperty("WOOD_TRADE_BARRIER", 0.05); //$1000/tC
+	public static final double INIT_CARBON_PRICE = IS_CARBON_ON ? getDoubleProperty("INIT_CARBON_PRICE", 0.02) : 0.0; // $1000/tC-eq
+	public static final double INIT_WOOD_PRICE = IS_FORESTRY_ON ? getDoubleProperty("INIT_WOOD_PRICE", 0.3) : 0.0; // $1000/tC-eq
+}
diff --git a/src/ac/ed/lurg/ModelMain.java b/src/ac/ed/lurg/ModelMain.java
index 89406182b07844433ddcc9c1e72a0bfaf1e214ff..83dc56064d0051ea3ae6b5cf5fa6112ce80da255 100644
--- a/src/ac/ed/lurg/ModelMain.java
+++ b/src/ac/ed/lurg/ModelMain.java
@@ -1,760 +1,809 @@
-package ac.ed.lurg;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-import ac.ed.lurg.carbon.CarbonFluxRasterSet;
-import ac.ed.lurg.carbon.CarbonFluxReader;
-import ac.ed.lurg.country.AbstractCountryAgent;
-import ac.ed.lurg.country.AnimalRateManager;
-import ac.ed.lurg.country.CompositeCountry;
-import ac.ed.lurg.country.CompositeCountryManager;
-import ac.ed.lurg.country.CountryAgentManager;
-import ac.ed.lurg.country.CountryBoundaryRaster;
-import ac.ed.lurg.country.CountryBoundaryReader;
-import ac.ed.lurg.country.CountryPrice;
-import ac.ed.lurg.country.GlobalPrice;
-import ac.ed.lurg.demand.AbstractDemandManager;
-import ac.ed.lurg.demand.BaseConsumpManager;
-import ac.ed.lurg.demand.CalorieManager;
-import ac.ed.lurg.demand.DemandManagerFromFile;
-import ac.ed.lurg.demand.DemandManagerSSP;
-import ac.ed.lurg.demand.ElasticDemandManager;
-import ac.ed.lurg.forestry.WoodYieldRasterSet;
-import ac.ed.lurg.landuse.ConversionCostReader;
-import ac.ed.lurg.landuse.CropUsageData;
-import ac.ed.lurg.landuse.CropUsageReader;
-import ac.ed.lurg.landuse.FPUManager;
-import ac.ed.lurg.landuse.InitProtectedAreasReader;
-import ac.ed.lurg.landuse.IrrigationConstraintReader;
-import ac.ed.lurg.landuse.IrrigationItem;
-import ac.ed.lurg.landuse.IrrigationMaxAmountReader;
-import ac.ed.lurg.landuse.IrrigationRasterSet;
-import ac.ed.lurg.landuse.IrrigiationCostReader;
-import ac.ed.lurg.landuse.LccKey;
-import ac.ed.lurg.landuse.LandCoverItem;
-import ac.ed.lurg.landuse.LandCoverReader;
-import ac.ed.lurg.landuse.LandCoverTile;
-import ac.ed.lurg.landuse.LandUseItem;
-import ac.ed.lurg.landuse.LandTileReader;
-import ac.ed.lurg.landuse.LandUseBinarySerializer;
-import ac.ed.lurg.landuse.MaxCropAreaReader;
-import ac.ed.lurg.landuse.ProtectedAreasReader;
-import ac.ed.lurg.landuse.RunOffReader;
-import ac.ed.lurg.landuse.WoodUsageData;
-import ac.ed.lurg.landuse.WoodUsageReader;
-import ac.ed.lurg.forestry.WoodYieldReader;
-import ac.ed.lurg.output.LandUseOutputer;
-import ac.ed.lurg.output.LpjgOutputer;
-import ac.ed.lurg.types.CommodityType;
-import ac.ed.lurg.types.CropType;
-import ac.ed.lurg.types.LandCoverType;
-import ac.ed.lurg.types.WoodType;
-import ac.ed.lurg.utils.FileWriterHelper;
-import ac.ed.lurg.utils.LogWriter;
-import ac.ed.lurg.yield.LPJYieldResponseMapReader;
-import ac.ed.lurg.yield.YieldRaster;
-import ac.sac.raster.IntegerRasterItem;
-import ac.sac.raster.IntegerRasterReader;
-import ac.sac.raster.RasterHeaderDetails;
-import ac.sac.raster.RasterKey;
-import ac.sac.raster.RasterOutputer;
-import ac.sac.raster.RasterSet;
-
-public class ModelMain {
-
-	private CountryAgentManager countryAgents;
-	private CountryBoundaryRaster countryBoundaryRaster;
-	private AbstractDemandManager demandManager;
-	private AnimalRateManager animalRateManager;
-	private CompositeCountryManager compositeCountryManager;
-	LPJYieldResponseMapReader lpjYieldReader;
-	private RasterHeaderDetails desiredProjection;
-
-	private InternationalMarket internationalMarket;
-	private IrrigationRasterSet currentIrrigationData;
-	private RasterSet<LandUseItem> globalLandUseRaster;
-	private RasterSet<IntegerRasterItem> clusterIdRaster;
-	private WoodYieldReader woodYieldReader;
-	private CarbonFluxReader carbonFluxReader;
-	
-
-	public static void main(String[] args) {
-		ModelMain theModel = new ModelMain();
-	    System.out.println("Working Directory = " + System.getProperty("user.dir"));
-		theModel.setup();
-		theModel.run();
-	}
-
-	/* setup models, reading inputs, etc. */
-	private void setup() {
-		desiredProjection = RasterHeaderDetails.getGlobalHeaderFromCellSize(ModelConfig.CELL_SIZE_X, ModelConfig.CELL_SIZE_Y, "999");
-
-		BaseConsumpManager baseConsumpManager = new BaseConsumpManager();
-		CalorieManager calorieManager = new CalorieManager();
-		compositeCountryManager = new CompositeCountryManager(baseConsumpManager);
-		lpjYieldReader = new LPJYieldResponseMapReader(desiredProjection);
-		animalRateManager = new AnimalRateManager(compositeCountryManager);
-		
-		if (ModelConfig.DEMAND_FROM_FILE)
-			demandManager = new DemandManagerFromFile(compositeCountryManager,calorieManager);
-		else if (ModelConfig.PRICE_ELASTIC_DEMAND)
-			demandManager = new ElasticDemandManager(ModelConfig.SSP_SCENARIO, baseConsumpManager,calorieManager, compositeCountryManager);
-		else
-			demandManager = new DemandManagerSSP(ModelConfig.SSP_SCENARIO, baseConsumpManager,calorieManager, compositeCountryManager);
-			
-		currentIrrigationData = getFixedIrrigationData();
-		countryBoundaryRaster = getCountryBoundaryRaster();
-		clusterIdRaster = ModelConfig.GENERATE_NEW_YIELD_CLUSTERS ? new RasterSet<IntegerRasterItem>(desiredProjection) : getClusterRaster();
-
-		globalLandUseRaster = new RasterSet<LandUseItem>(desiredProjection);
-		internationalMarket = new InternationalMarket();
-		
-		woodYieldReader = new WoodYieldReader(desiredProjection);
-		carbonFluxReader = new CarbonFluxReader(desiredProjection);
-		
-		createCountryAgents(compositeCountryManager.getAll());
-	}
-
-	/* run the model */
-	private void run() {
-		for (int i = ModelConfig.START_TIMESTEP; i <= ModelConfig.END_TIMESTEP; i++) {
-			Timestep timestep = new Timestep(i);
-			try {
-				doTimestep(timestep);
-			} catch (Exception e) {
-				LpjgOutputer.writeMarkerFile(timestep.getYear(), true);
-				throw new RuntimeException(e);
-			}
-		}
-	}
-	
-	private void doTimestep(Timestep timestep) {
-		System.gc();
-		LogWriter.println("Timestep: " + timestep.toString());
-
-		YieldRaster yieldSurfaces = getYieldSurfaces(timestep); // this will wait for the marker file from LPJ if configured to do so
-		getUpdateIrrigationData(timestep, yieldSurfaces); // updating currentIrrigationData
-
-		// When running half earth we can to alter protected areas data at a point in time
-		if(ModelConfig.HALFEARTH && ModelConfig.FORCE_PROTECTED_AREAS_START_YEAR == timestep.getYear() && !ModelConfig.IS_CALIBRATION_RUN) {
-			new ProtectedAreasReader(globalLandUseRaster).getRasterDataFromFile(ModelConfig.HALF_EARTH_FILE);
-			countryAgents.updateProtectedAreasForAll(globalLandUseRaster);
-		}
-		
-		double previousCarbonDemand = (timestep.isInitialTimestep() || ModelConfig.IS_CALIBRATION_RUN ) ? 0: demandManager.getCarbonDemand(timestep.getPreviousTimestep());
-		double carbonDemand = demandManager.getCarbonDemand(ModelConfig.IS_CALIBRATION_RUN ? new Timestep(1) : timestep);
-		double carbonDemandIncrease = (carbonDemand > previousCarbonDemand) ? carbonDemand - previousCarbonDemand: 0;
-		
-		WoodYieldRasterSet woodYieldData = getWoodYieldData(timestep);
-		CarbonFluxRasterSet carbonFluxData = getCarbonFluxData(timestep);
-
-		Map<LccKey, Double> conversionCosts;
-		conversionCosts = (ModelConfig.CONVERSION_COSTS_FROM_FILE) ? new ConversionCostReader().read() : new ConversionCostReader().calcFromConfig();
-		
-		countryAgents.determineProductionForAll(timestep, yieldSurfaces, currentIrrigationData, carbonFluxData, woodYieldData,
-				conversionCosts, carbonDemandIncrease);
-		internationalMarket.determineInternationalTrade(countryAgents.getAll(), carbonDemand, timestep);
-		
-		int i = 0;		
-		while (i < ModelConfig.DEMAND_RECALC_MAX_ITERATIONS || 
-				(ModelConfig.DEMAND_RECALC_ON_NEGATIVE_STOCK && internationalMarket.negativeStockLevelsExist() && i<10)) { // loop if negative stock have we haven't tried 10 times already
-			LogWriter.println("\n++ Re-estimating prices and demand: timestep " + timestep.getTimestep() + ": interation " + i);
-			countryAgents.recalculateDemandAndNetImportsForAll(); // recalculate demand from new prices and calculate imports and exports
-			internationalMarket.determineInternationalTrade(countryAgents.getAll(), carbonDemand, timestep); // calculate prices
-			i++;
-		}
-		internationalMarket.applyPriceShocks(timestep);
-		
-		// output results
-		outputTimestepResults(timestep);
-		checkAndSaveCheckpoint(timestep);
-
-	}
-	
-	private void checkAndSaveCheckpoint(Timestep timestep) {
-		if (ModelConfig.IS_CALIBRATION_RUN || shouldSaveCheckpoint(timestep)) 
-			serializeCheckpoint();
-	}
-	
-	private boolean shouldSaveCheckpoint(Timestep timestep) {
-		if (ModelConfig.CHECKPOINT_YEARS != null) {
-			LogWriter.println("Looking to see if checkpoint year reached " + ModelConfig.CHECKPOINT_YEARS);
-
-			String[] yearStr = ModelConfig.CHECKPOINT_YEARS.split(",");
-			for(int i=0; i<yearStr.length; i++) {
-				LogWriter.println("Got a checkpoint yearStr " + yearStr[i]);
-				int year = Integer.parseInt(yearStr[i]);
-				if (timestep.getYear() == year)
-					return true;
-			}
-		}
-		return false;
-	}
-
-	private void writeLandCoverFile(Timestep timestep, RasterSet<LandUseItem> landUseRaster) {
-		try {
-			StringBuffer sbHeadings = new StringBuffer("Year,Cropland,Pasture,ManForest,UnmanForest,Natural,AbPasture,Suitable,EnergyCrop,FertCrop,IrrigCrop");
-			BufferedWriter outputFile = FileWriterHelper.getFileWriter(timestep, ModelConfig.LAND_COVER_OUTPUT_FILE, sbHeadings.toString());
-
-			StringBuffer sbData = new StringBuffer();
-			sbData.append(String.format("%d,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f", timestep.getYear(),
-					LandUseItem.getTotalLandCover(landUseRaster.values(), LandCoverType.CROPLAND),
-					LandUseItem.getTotalLandCover(landUseRaster.values(), LandCoverType.PASTURE),
-					LandUseItem.getTotalLandCover(landUseRaster.values(), LandCoverType.TIMBER_FOREST),
-					LandUseItem.getTotalLandCover(landUseRaster.values(), LandCoverType.CARBON_FOREST),
-					LandUseItem.getTotalLandCover(landUseRaster.values(), LandCoverType.NATURAL),
-					LandUseItem.getAbandonedPasture(landUseRaster.values()),
-					LandUseItem.getSuitableTotal(landUseRaster.values(), timestep.getYear()))
-					);
-						
-			sbData.append(String.format(",%.1f", LandUseItem.getTotalCropArea(landUseRaster.values(), CropType.ENERGY_CROPS)));
-			sbData.append(String.format(",%.1f", LandUseItem.getFertiliserTotal(landUseRaster.values(), CropType.getCropsLessPasture()) / 1000));
-			sbData.append(String.format(",%.1f", LandUseItem.getIrrigationTotal(landUseRaster.values(), CropType.getCropsLessPasture())));
-
-			outputFile.write(sbData.toString());
-			outputFile.newLine();
-			outputFile.close();
-		} catch (IOException e) {
-			LogWriter.print(e);
-		}
-	}
-
-	private void writeGlobalMarketFile(Timestep timestep) {
-		try {
-			BufferedWriter outputFile = FileWriterHelper.getFileWriter(timestep, ModelConfig.PRICES_OUTPUT_FILE, "Year,Crop,Imports (Mt),Exports (Mt),New export price, Stock Levels (Mt)");
-			internationalMarket.writeGlobalMarketFile(timestep, outputFile);
-			outputFile.close();
-		} catch (IOException e) {
-			LogWriter.print(e);
-		}
-	}
-	
-	private void writeGlobalFoodBalanceSheet(Timestep timestep, RasterSet<LandUseItem> landUseRaster) {
-		try {
-			BufferedWriter outputFile = FileWriterHelper.getFileWriter(timestep, ModelConfig.FOOD_BALANCE_SHEET_FILE, "Year,Crop,Production,Imports,Export,TransportLosses,StockVar,Supply,MonogastricsFeed,RuminantsFeed,SeedAndOtherLosses,FoodAnd1stGen,ProdArea");
-			Map<CropType, GlobalPrice> worldPrices = internationalMarket.getWorldPrices();
-			double harvestedAndFallowArea = 0;
-			
-			for (CropType crop : CropType.getCropsLessPasture()) {
-				GlobalPrice priceQuantity = worldPrices.get(crop); // some specific logic for import/exports and this has been aggregated already, so best to use it
-				double prod=0, prodArea=0, feedMonogastrics=0, feedRuminants=0;
-				double exportsBeforeTL=0, imports=0, transportloss=0, stockChange=0;
-				
-				if (priceQuantity != null) {
-					exportsBeforeTL = priceQuantity.getExportsBeforeTransportLoss();
-					imports = priceQuantity.getImportAmount();
-					transportloss = priceQuantity.getTransportLosses();
-					stockChange = priceQuantity.getStockChange();
-				}
-				
-				for (AbstractCountryAgent ca : countryAgents.getAll()) {
-					Map<CropType, CropUsageData> allCropUsage = ca.getCropUsageData();
-					CropUsageData cropUsage = allCropUsage.get(crop);
-					if (cropUsage != null) {
-						prod += cropUsage.getProductionExpected();
-						prodArea += cropUsage.getArea();
-						feedMonogastrics += cropUsage.getMonogastricFeed();
-						feedRuminants += cropUsage.getRuminantFeed();
-					}
-				}
-
-				double seedAndWaste = prod * crop.getSeedAndWasteRate();
-				double netSupply = prod - exportsBeforeTL + imports;
-				double foodAnd1stGen = netSupply - feedMonogastrics - feedRuminants - seedAndWaste;
-				
-				if (!crop.equals(CropType.SETASIDE))
-					prodArea *= (1-ModelConfig.UNHANDLED_CROP_RATE);  // remove unhandled crop area
-				
-				harvestedAndFallowArea += prodArea;
-				
-				StringBuffer sbData = new StringBuffer();
-				sbData.append(String.format("%d,%s", timestep.getYear(), crop.getGamsName()));
-				sbData.append(String.format(",%.2f", prod));
-				sbData.append(String.format(",%.2f,%.2f,%.2f,%.2f", imports, exportsBeforeTL, transportloss, stockChange));
-				sbData.append(String.format(",%.2f,%.2f,%.2f,%.2f,%.2f", netSupply, feedMonogastrics, feedRuminants, seedAndWaste, foodAnd1stGen));
-				sbData.append(String.format(",%.2f", prodArea));
-				outputFile.write(sbData.toString());
-				outputFile.newLine();
-			}
-
-			double cropLandArea = LandUseItem.getTotalLandCover(landUseRaster.values(), LandCoverType.CROPLAND);
-			double unhandledArea = cropLandArea - harvestedAndFallowArea;
-			outputFile.write(String.format("%d,%s,,,,,,,,,,,%.2f", timestep.getYear(), "unhandled", unhandledArea));
-			outputFile.newLine();
-
-			outputFile.close();
-		} catch (IOException e) {
-			LogWriter.print(e);
-		}
-	}
-
-	private void writeDemandFile(Timestep timestep) {
-		try {
-			BufferedWriter outputFile = FileWriterHelper.getFileWriter(timestep, ModelConfig.DEMAND_OUTPUT_FILE, "Year,Commodity,Amount (Mt)");
-
-			for (CommodityType comm : CommodityType.getAllFoodItems()) {
-				double demandAmount = 0;
-
-				for (AbstractCountryAgent country : countryAgents.getAll()) {
-					Map<CommodityType, Double> demands = country.getCurrentProjectedDemand();
-					if (demands == null) {
-						LogWriter.printlnError(country.getCountry() + " " + comm);
-					}
-					
-					Double d = demands.get(comm);
-					if (d != null) {
-						demandAmount += d.doubleValue();
-						LogWriter.println(String.format("%s,%s,%.4f", country.getCountry(), comm.getGamsName(), d));
-					}
-				}
-				StringBuffer sbData = new StringBuffer();
-				sbData.append(String.format("%d,%s", timestep.getYear(), comm.getGamsName()));
-				sbData.append(String.format(",%.1f", demandAmount));
-
-				LogWriter.println("Global demand " + timestep.getYear() + " " + comm.getGamsName() + " " + demandAmount + "\n");
-				outputFile.write(sbData.toString());
-				outputFile.newLine();
-			}
-			
-			double gen2EcDemand = countryAgents.getAll().stream().mapToDouble(c -> c.getCurrentGen2EcDemand()).sum();
-			outputFile.write(String.format("%d,%s,%.1f", timestep.getYear(), CropType.ENERGY_CROPS.getGamsName(), gen2EcDemand));
-			outputFile.newLine();
-			
-			outputFile.close();
-		} catch (IOException e) {
-			LogWriter.print(e);
-		}
-	}
-
-	private void writeDomesticProductionFile(Timestep timestep) {
-		try {
-			StringBuffer sbHeadings = new StringBuffer("Year, Country, Crop, Area, Production, Production_cost, Import_price, Export_price, Consumer_price, Net_imports, Net_import_cost, Prod_shock, Rum_feed_amount, Mon_feed_amount");
-			BufferedWriter outputFile = FileWriterHelper.getFileWriter(timestep, ModelConfig.DOMESTIC_OUTPUT_FILE, sbHeadings.toString());
-
-			for (CropType crop : CropType.getAllItems()) {
-				for (AbstractCountryAgent country : countryAgents.getAll()) {
-					
-					Map<CropType, CropUsageData> cropUsageAllCrops = country.getCropUsageData();
-					CropUsageData cropUsage = cropUsageAllCrops.get(crop);
-					
-					if (cropUsage == null) 
-						continue;
-					
-					double prodCosts = cropUsage.getTotalProdCost();
-					double prod = cropUsage.getProductionExpected();
-					double prodShock = cropUsage.getProductionShock();
-					double area = cropUsage.getArea();
-					double rumFeedAmount = cropUsage.getRuminantFeed();
-					double monFeedAmount = cropUsage.getMonogastricFeed();
-					
-					double importPrice = 0;
-					double exportPrice = 0 ;
-					double consumerPrice = 0;
-					double netImports = 0;
-					double netImportCost = 0;
-
-					if (crop.isImportedCrop()) {
-						CountryPrice px = country.getCurrentCountryPrices().get(crop);
-						importPrice = px.getImportPrice(); 
-						exportPrice = px.getExportPrice();
-						consumerPrice = px.getConsumerPrice();
-						netImports = cropUsage.getNetImportsExpected();	//this isn't accounting for transport losses in exports 
-						netImportCost = cropUsage.getNetImportCostExpected();
-					}
-	
-					StringBuffer sbData = new StringBuffer();
-					sbData.append(String.format("%d,%s,%s", timestep.getYear(), country.getCountry(), crop.getGamsName()));
-					sbData.append(String.format(",%.4f,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f", area, prod, prodCosts, importPrice, exportPrice, consumerPrice, netImports, netImportCost, prodShock, rumFeedAmount, monFeedAmount));
-
-					outputFile.write(sbData.toString());
-					outputFile.newLine();
-				}
-			}
-			outputFile.close();
-
-		} catch (IOException e) {
-			LogWriter.print(e);
-		}
-	}
-
-	private void writeCountryDemandFile(Timestep timestep){
-
-		try {
-			StringBuffer sbHeadings = new StringBuffer("Year, Country, Commodity, Demand, BioenergyDemand");
-			BufferedWriter outputFile = FileWriterHelper.getFileWriter(timestep, ModelConfig.COUNTRY_DEMAND_FILE, sbHeadings.toString());
-
-			for (AbstractCountryAgent country : countryAgents.getAll()) {
-				for (CommodityType commodity : CommodityType.getAllFoodItems()) {
-					double bioenergyDemand = demandManager.getFirstGenBioenergyDemand(country.getCountry(), timestep.getYear(), commodity);
-					double  demand =  country.getCurrentProjectedDemand().get(commodity);
-
-					StringBuffer sbData = new StringBuffer();
-					sbData.append(String.format("%d,%s,%s", timestep.getYear(), country.getCountry(), commodity.getGamsName()));
-					sbData.append(String.format(",%.3f,%.3f", demand, bioenergyDemand));
-
-					outputFile.write(sbData.toString());
-					outputFile.newLine();
-				}
-			}
-			outputFile.close();
-
-		} catch (IOException e) {
-			LogWriter.print(e);
-		}
-	}
-	
-	private void writeAnimalNumber(Timestep timestep) {
-		try {
-			StringBuffer sbHeadings = new StringBuffer("Year,Country,FAOItem,Heads(M)");
-			BufferedWriter outputFile = FileWriterHelper.getFileWriter(timestep, ModelConfig.ANIMAL_NUMBERS_OUTPUT_FILE, sbHeadings.toString());
-		
-			for (AbstractCountryAgent country : countryAgents.getAll()) {
-				Map<CropType, CropUsageData> cropUsageAllCrops = country.getCropUsageData();
-				for (CropType crop : CropType.getMeatTypes()) {
-					CropUsageData cropusage = cropUsageAllCrops.get(crop);
-					if (cropusage == null)
-						continue;
-					double prod = cropusage.getProductionExpected();
-					
-					Map<String, Double> animalRates = animalRateManager.getAnimalRates(country.getCountry(), crop);
-					for (Entry<String, Double> entry : animalRates.entrySet()) {
-						StringBuffer sbData = new StringBuffer();
-						double animalNum = prod * entry.getValue();
-						sbData.append(String.format("%d,%s,%s,%.4f", timestep.getYear(), country.getCountry(), entry.getKey(), animalNum));		
-						outputFile.write(sbData.toString());
-						outputFile.newLine();
-					}
-			}
-		}
-		outputFile.close();
-			
-		} catch (IOException e) {
-			LogWriter.print(e);
-		}
-	}
-	
-	private void outputTimestepResults(Timestep timestep) {
-
-		writeLandCoverFile(timestep, globalLandUseRaster);
-		writeGlobalMarketFile(timestep);
-		writeDemandFile(timestep);
-		writeDomesticProductionFile(timestep);
-		writeCountryDemandFile(timestep);
-		writeGlobalFoodBalanceSheet(timestep, globalLandUseRaster);
-		writeAnimalNumber(timestep);
-
-		if (ModelConfig.OUTPUT_FOR_LPJG) {
-			for (int outputYear : timestep.getYearsFromLast()) {
-				LogWriter.println("Outputing Year: " + outputYear);
-				RasterSet<LandUseItem> landUseToOutput = null;
-
-				if (outputYear == timestep.getYear()) {
-					landUseToOutput = globalLandUseRaster;
-				} 
-				else if (ModelConfig.INTERPOLATE_OUTPUT_YEARS) {
-					// we run with 1 year time steps these days so this code is redundant and flawed
-			/*		InterpolatingRasterSet<LandUseItem> intermediateLandUse = new InterpolatingRasterSet<LandUseItem>( landUseRaster.getHeaderDetails()) {
-						private static final long serialVersionUID = 1306045141011047760L;
-						protected LandUseItem createRasterData() {
-							return new LandUseItem();
-						}
-					};
-					intermediateLandUse.setup(globalLandUseRaster, landUseRaster, timestep.getPreviousTimestep().getYear(), timestep.getYear(), outputYear);
-					landUseToOutput = intermediateLandUse; */
-				}
-
-				if (landUseToOutput != null) {
-					LpjgOutputer lpjOutputer = new LpjgOutputer(outputYear, landUseToOutput);
-					lpjOutputer.writeOutput();
-				}
-			}
-			outputWaterAvailablity(timestep, currentIrrigationData);  // uses the year directory structure created above
-		}
-
-		if (timestep.isInitialTimestep() && ModelConfig.GENERATE_NEW_YIELD_CLUSTERS)
-			outputClusters(clusterIdRaster);
-
-		// Output LandUses to tabular file, for analysis (perhaps)
-		LogWriter.println("Outputing land uses Year: " + timestep.getYear());
-		LandUseOutputer landuseOutputer = new LandUseOutputer(timestep.getYear(), globalLandUseRaster);
-		landuseOutputer.writeOutput();
-
-		// don't really need this a LPJ outputs have same data, although in a slightly different format
-		// outputLandCover(timestep.getYear(), landUseRaster, LandCoverType.CROPLAND);
-		// outputLandCover(timestep.getYear(), landUseRaster, LandCoverType.PASTURE);
-	}
-
-
-	private void outputWaterAvailablity(Timestep timestep, IrrigationRasterSet irrigiationRS) {
-		new RasterOutputer<Double, IrrigationItem>(irrigiationRS, ModelConfig.OUTPUT_DIR + File.separator + timestep.getYear() + File.separator + "IrrigConstraint.asc") {
-			@Override
-			public Double getValue(RasterKey location) {
-				IrrigationItem item = results.get(location);
-				if (item == null)
-					return null;
-
-				return item.getIrrigConstraint();
-			}
-		}.writeOutput();
-	}
-
-	private void outputClusters(RasterSet<IntegerRasterItem> landUseRaster) {
-		new RasterOutputer<Integer, IntegerRasterItem>(landUseRaster, ModelConfig.CLUSTERED_YIELD_FILE) {
-			@Override
-			public Integer getValue(RasterKey location) {
-				IntegerRasterItem item = results.get(location);
-				if (item == null)
-					return null;
-
-				return item.getInt();
-			}
-		}.writeOutput();
-	}
-
-	public RasterSet<IntegerRasterItem> getClusterRaster() {
-		RasterSet<IntegerRasterItem> clusters = new RasterSet<IntegerRasterItem>(desiredProjection) {
-			private static final long serialVersionUID = 2467452274591854417L;
-
-			@Override
-			protected IntegerRasterItem createRasterData() {
-				return new IntegerRasterItem(0);
-			}
-		};
-
-		IntegerRasterReader clusterReader = new IntegerRasterReader(clusters);
-		clusterReader.getRasterDataFromFile(ModelConfig.CLUSTERED_YIELD_FILE);
-		return clusters;
-	}
-
-	public CountryBoundaryRaster getCountryBoundaryRaster() {
-		CountryBoundaryRaster countryBoundaries = new CountryBoundaryRaster(desiredProjection, compositeCountryManager);
-		CountryBoundaryReader countryReader = new CountryBoundaryReader(countryBoundaries);
-		countryReader.getRasterDataFromFile(ModelConfig.COUNTRY_BOUNDARY_FILE);
-		return countryBoundaries;
-	}
-
-	public void createCountryAgents(Collection<CompositeCountry> countryGrouping) {
-		countryAgents = new CountryAgentManager(compositeCountryManager, demandManager, countryBoundaryRaster, internationalMarket, clusterIdRaster, globalLandUseRaster);
-		Map<CompositeCountry, Map<CropType, CropUsageData>> cropUsageDataMap = getInitialCropUsageData();
-		Map<CompositeCountry, Map<WoodType, WoodUsageData>> woodUsageDataMap = getInitialWoodUsageData();
-		RasterSet<LandUseItem> initLU = getInitialLandUse();
-		
-		for (CompositeCountry cc : countryGrouping) {
-			countryAgents.addForCountry(cc, cropUsageDataMap, initLU, woodUsageDataMap);
-			globalLandUseRaster.putAll(initLU);
-		}
-	}
-
-	private RasterSet<LandUseItem> getInitialLandUse() {
-		RasterSet<LandUseItem> initialLU;
-		if (ModelConfig.IS_CALIBRATION_RUN)
-			initialLU = getLandUseFromBaseline();
-		else
-			initialLU = deserializeLandUse();
-
-		return initialLU;
-	}
-	
-	private Map<CompositeCountry, Map<CropType, CropUsageData>> getInitialCropUsageData() {
-		Map<CompositeCountry, Map<CropType, CropUsageData>> cropUsageDataMap;
-		if (ModelConfig.IS_CALIBRATION_RUN)
-			cropUsageDataMap = new CropUsageReader(compositeCountryManager).getCommodityData();
-		else
-			cropUsageDataMap = deserializeCropUsage();
-
-		return cropUsageDataMap;
-	}
-	
-	private Map<CompositeCountry, Map<WoodType, WoodUsageData>> getInitialWoodUsageData() {
-		Map<CompositeCountry, Map<WoodType, WoodUsageData>> woodUsageDataMap;
-		if (ModelConfig.IS_CALIBRATION_RUN) {
-			woodUsageDataMap = new WoodUsageReader(compositeCountryManager).getWoodUsageData();
-
-		} else {
-			woodUsageDataMap = deserializeWoodUsage();
-		}
-		return woodUsageDataMap;
-	}
-	
-	@SuppressWarnings("unchecked")
-	private Map<CompositeCountry, Map<WoodType, WoodUsageData>> deserializeWoodUsage() {
-		try {
-			Map<CompositeCountry, Map<WoodType, WoodUsageData>> woodUsageDataMap;
-			FileInputStream fileIn = new FileInputStream(ModelConfig.SERIALIZED_WOOD_USAGE_FILE);
-			ObjectInputStream in = new ObjectInputStream(fileIn);
-			woodUsageDataMap = (Map<CompositeCountry, Map<WoodType, WoodUsageData>>) in.readObject();
-			in.close();
-			fileIn.close();
-			LogWriter.println("Deserialized " + ModelConfig.SERIALIZED_WOOD_USAGE_FILE);
-			return woodUsageDataMap;
-		} catch (IOException i) {
-			LogWriter.printlnError("Problem deserializing " + ModelConfig.SERIALIZED_WOOD_USAGE_FILE);
-			LogWriter.print(i);
-			return null;
-		} catch (ClassNotFoundException c) {
-			LogWriter.printlnError("Map<CompositeCountry, Double[]> not found");
-			c.printStackTrace();
-			return null;
-		}
-	}
-
-	private void serializeLandUse(RasterSet<LandUseItem> landUseRaster) {
-		RasterSet<LandUseItem> rasterToSerialise = new RasterSet<LandUseItem>(desiredProjection);
-		for (Map.Entry<RasterKey, LandUseItem> entry : landUseRaster.entrySet()) {
-			LandUseItem newLuItem = new LandUseItem(entry.getValue());
-			newLuItem.overwriteLandCoverAreas(new HashMap<LandCoverType, LandCoverTile>()); // Clear data
-			rasterToSerialise.put(entry.getKey(), newLuItem);
-		}
-		try {
-			LogWriter.println("Starting serializing LandUse to " + ModelConfig.SERIALIZED_LAND_USE_FILE);
-			FileOutputStream fileOut = new FileOutputStream(ModelConfig.SERIALIZED_LAND_USE_FILE);
-			ObjectOutputStream out = new ObjectOutputStream(fileOut);
-			out.writeObject(rasterToSerialise);
-			out.close();
-			fileOut.close();
-			LogWriter.println("Serialized data is saved");
-		} catch (IOException i) {
-			i.printStackTrace();
-		}
-
-		LogWriter.println("Starting serializing LandCover to " + ModelConfig.SERIALIZED_LAND_COVER_FILE);
-		LandUseBinarySerializer luSer = new LandUseBinarySerializer();
-		luSer.serializeLandUse(globalLandUseRaster);
-		LogWriter.println("LandCover data is saved");
-	}
-
-	@SuppressWarnings("unchecked")
-	private RasterSet<LandUseItem> deserializeLandUse() {
-		try {
-			RasterSet<LandUseItem> initLU;
-			FileInputStream fileIn = new FileInputStream(ModelConfig.SERIALIZED_LAND_USE_FILE);
-			ObjectInputStream in = new ObjectInputStream(fileIn);
-			initLU = (RasterSet<LandUseItem>) in.readObject();
-			in.close();
-			fileIn.close();
-			LogWriter.println("Deserialized " + ModelConfig.SERIALIZED_LAND_USE_FILE);
-			LandUseBinarySerializer luSer = new LandUseBinarySerializer();
-			luSer.deserializeLandUse(initLU);
-			return initLU;
-		} catch (IOException i) {
-			LogWriter.printlnError("Problem deserializing " + ModelConfig.SERIALIZED_LAND_USE_FILE);
-			LogWriter.print(i);
-			return null;
-		} catch (ClassNotFoundException c) {
-			LogWriter.printlnError("RasterSet<LandUseItem> class not found");
-			c.printStackTrace();
-			return null;
-		}
-	}
-	
-	@SuppressWarnings("unchecked")
-	private Map<CompositeCountry, Map<CropType, CropUsageData>> deserializeCropUsage() {
-		try {
-			Map<CompositeCountry, Map<CropType, CropUsageData>> initCropUsage;
-			FileInputStream fileIn = new FileInputStream(ModelConfig.SERIALIZED_CROP_USAGE_FILE);
-			ObjectInputStream in = new ObjectInputStream(fileIn);
-			initCropUsage = (Map<CompositeCountry, Map<CropType, CropUsageData>>) in.readObject();
-			in.close();
-			fileIn.close();
-			LogWriter.println("Deserialized " + ModelConfig.SERIALIZED_CROP_USAGE_FILE);
-			return initCropUsage;
-		} catch (IOException i) {
-			LogWriter.printlnError("Problem deserializing " + ModelConfig.SERIALIZED_CROP_USAGE_FILE);
-			LogWriter.print(i);
-			return null;
-		} catch (ClassNotFoundException c) {
-			LogWriter.printlnError("Map<CompositeCountry, Map<CropType, CropUsageData>> not found");
-			c.printStackTrace();
-			return null;
-		}
-	}
-	
-
-	/** this is if we are starting from Hurtt of other initial land covers (so we don't have land uses and intensity data) */
-	private RasterSet<LandUseItem> getLandUseFromBaseline() {
-		RasterSet<LandCoverItem> initialLC = new RasterSet<LandCoverItem>(desiredProjection) {
-			private static final long serialVersionUID = 4642550777741425501L;
-
-			protected LandCoverItem createRasterData() {
-				return new LandCoverItem();
-			}
-		};
-
-		new MaxCropAreaReader(initialLC).getRasterDataFromFile(ModelConfig.HIGH_SLOPE_AREAS_FILE); // Fraction unavailable for conversion
-		new LandCoverReader(initialLC).getRasterDataFromFile(ModelConfig.INITAL_LAND_COVER_FILE); // Land cover fractions
-		new LandTileReader(initialLC).getRasterDataFromFile(ModelConfig.LAND_COVER_AGE_DIST_FILENAME); // Age distribution of land cover
-		new InitProtectedAreasReader(initialLC).getRasterDataFromFile(ModelConfig.PROTECTED_AREAS_FILE); // Protected fraction
-
-		RasterSet<LandUseItem> landUseRaster = new RasterSet<LandUseItem>(initialLC.getHeaderDetails());
-		
-		for (Map.Entry<RasterKey, LandCoverItem> entry : initialLC.entrySet()) {
-			//LogWriter.println(initialLC.getXCoordin(entry.getKey()) + " " + initialLC.getYCoordin(entry.getKey()));
-			landUseRaster.put(entry.getKey(), new LandUseItem(entry.getValue()));
-		}
-
-		return landUseRaster;
-	}
-
-	private YieldRaster getYieldSurfaces(Timestep timestep) {
-		return lpjYieldReader.getRasterData(timestep);
-	}
-
-	/** Get irrigation data that does not change with time, should only be called once */
-	private IrrigationRasterSet getFixedIrrigationData() {
-		IrrigationRasterSet fixedIrrigData = new IrrigationRasterSet(desiredProjection, new FPUManager(desiredProjection));
-		new IrrigiationCostReader(fixedIrrigData).getRasterDataFromFile(ModelConfig.IRRIGATION_COST_FILE);
-		new IrrigationConstraintReader(fixedIrrigData).getRasterDataFromFile(ModelConfig.IRRIGATION_CONSTRAINT_FILE);
-		
-		String baseTimestepRootDir = Timestep.getYearSubDir(ModelConfig.YIELD_DIR, ModelConfig.ELLIOTT_BASEYEAR); // needs to be Elliott base timestep
-		new RunOffReader(fixedIrrigData, true).getRasterDataFromFile(baseTimestepRootDir + File.separator + ModelConfig.IRRIG_RUNOFF_FILE);
-		
-		fixedIrrigData.calcIrrigConstraintOffsets();  // should have everything we need to calc offset between Elliott and LPJ data
-		return fixedIrrigData;
-	}
-	
-	/** Get carbon flux data */
-	private CarbonFluxRasterSet getCarbonFluxData(Timestep timestep) {
-		if (ModelConfig.IS_CARBON_ON) {
-			return carbonFluxReader.getCarbonFluxes(globalLandUseRaster, timestep);
-		} else {
-			return new CarbonFluxRasterSet(desiredProjection);
-		}
-	}
-	
-	/** Get wood yield data */
-	private WoodYieldRasterSet getWoodYieldData(Timestep timestep) {
-		if (ModelConfig.IS_FORESTRY_ON) {
-			return woodYieldReader.getWoodYields(globalLandUseRaster, timestep, internationalMarket.getWoodPrice().getExportPrice());	
-		} else {
-			return new WoodYieldRasterSet(desiredProjection);
-		}		
-	}
-
-	/** Ugly in situ update of currentIrrigationData, better if IrrigationRasterSets were handled more immutably */
-	private void getUpdateIrrigationData(Timestep timestep, YieldRaster yieldSurfaces) {
-		String rootDir = timestep.getYearSubDir(ModelConfig.YIELD_DIR);
-		
-		new IrrigationMaxAmountReader(currentIrrigationData).getRasterDataFromFile(rootDir + File.separator + ModelConfig.IRRIG_MAX_WATER_FILENAME);
-
-		if (!ModelConfig.USE_BLUE_WATER_FILE_IRRIG_CONSTRAINT) {
-			new RunOffReader(currentIrrigationData, false).getRasterDataFromFile(rootDir + File.separator + ModelConfig.IRRIG_RUNOFF_FILE);
-			currentIrrigationData.updateIrrigConstraints(timestep);
-		}
-	}
-	
-	private void serializeCheckpoint() {
-		serializeLandUse(globalLandUseRaster);
-		countryAgents.serializeCropUsageForAll();
-		countryAgents.serializeWoodUsageForAll();
-		internationalMarket.serializeGlobalPrices();
-	}
-}
+package ac.ed.lurg;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import ac.ed.lurg.carbon.CarbonFluxRasterSet;
+import ac.ed.lurg.carbon.CarbonFluxReader;
+import ac.ed.lurg.country.AbstractCountryAgent;
+import ac.ed.lurg.country.AnimalRateManager;
+import ac.ed.lurg.country.CompositeCountry;
+import ac.ed.lurg.country.CompositeCountryManager;
+import ac.ed.lurg.country.CountryAgentManager;
+import ac.ed.lurg.country.CountryBoundaryRaster;
+import ac.ed.lurg.country.CountryBoundaryReader;
+import ac.ed.lurg.country.CountryPrice;
+import ac.ed.lurg.country.GlobalPrice;
+import ac.ed.lurg.demand.AbstractDemandManager;
+import ac.ed.lurg.demand.BaseConsumpManager;
+import ac.ed.lurg.demand.CalorieManager;
+import ac.ed.lurg.demand.DemandManagerFromFile;
+import ac.ed.lurg.demand.DemandManagerSSP;
+import ac.ed.lurg.demand.ElasticDemandManager;
+import ac.ed.lurg.forestry.WoodYieldRasterSet;
+import ac.ed.lurg.landuse.ConversionCostReader;
+import ac.ed.lurg.landuse.CropUsageData;
+import ac.ed.lurg.landuse.CropUsageReader;
+import ac.ed.lurg.landuse.FPUManager;
+import ac.ed.lurg.landuse.InitProtectedAreasReader;
+import ac.ed.lurg.landuse.IrrigationConstraintReader;
+import ac.ed.lurg.landuse.IrrigationItem;
+import ac.ed.lurg.landuse.IrrigationMaxAmountReader;
+import ac.ed.lurg.landuse.IrrigationRasterSet;
+import ac.ed.lurg.landuse.IrrigiationCostReader;
+import ac.ed.lurg.landuse.LccKey;
+import ac.ed.lurg.landuse.LandCoverItem;
+import ac.ed.lurg.landuse.LandCoverReader;
+import ac.ed.lurg.landuse.LandCoverTile;
+import ac.ed.lurg.landuse.LandUseItem;
+import ac.ed.lurg.landuse.LandTileReader;
+import ac.ed.lurg.landuse.LandUseBinarySerializer;
+import ac.ed.lurg.landuse.MaxCropAreaReader;
+import ac.ed.lurg.landuse.ProtectedAreasReader;
+import ac.ed.lurg.landuse.RunOffReader;
+import ac.ed.lurg.landuse.WoodUsageData;
+import ac.ed.lurg.landuse.WoodUsageReader;
+import ac.ed.lurg.forestry.WoodYieldReader;
+import ac.ed.lurg.output.LandUseOutputer;
+import ac.ed.lurg.output.LpjgOutputer;
+import ac.ed.lurg.types.CommodityType;
+import ac.ed.lurg.types.CropType;
+import ac.ed.lurg.types.LandCoverType;
+import ac.ed.lurg.types.WoodType;
+import ac.ed.lurg.utils.FileWriterHelper;
+import ac.ed.lurg.utils.LogWriter;
+import ac.ed.lurg.yield.LPJYieldResponseMapReader;
+import ac.ed.lurg.yield.YieldRaster;
+import ac.sac.raster.IntegerRasterItem;
+import ac.sac.raster.IntegerRasterReader;
+import ac.sac.raster.RasterHeaderDetails;
+import ac.sac.raster.RasterKey;
+import ac.sac.raster.RasterOutputer;
+import ac.sac.raster.RasterSet;
+
+public class ModelMain {
+
+	private CountryAgentManager countryAgents;
+	private CountryBoundaryRaster countryBoundaryRaster;
+	private AbstractDemandManager demandManager;
+	private AnimalRateManager animalRateManager;
+	private CompositeCountryManager compositeCountryManager;
+	LPJYieldResponseMapReader lpjYieldReader;
+	private RasterHeaderDetails desiredProjection;
+
+	private InternationalMarket internationalMarket;
+	private IrrigationRasterSet currentIrrigationData;
+	private RasterSet<LandUseItem> globalLandUseRaster;
+	private RasterSet<IntegerRasterItem> clusterIdRaster;
+	private WoodYieldReader woodYieldReader;
+	private CarbonFluxReader carbonFluxReader;
+	
+
+	public static void main(String[] args) {
+		ModelMain theModel = new ModelMain();
+	    System.out.println("Working Directory = " + System.getProperty("user.dir"));
+		theModel.setup();
+		theModel.run();
+	}
+
+	/* setup models, reading inputs, etc. */
+	private void setup() {
+		desiredProjection = RasterHeaderDetails.getGlobalHeaderFromCellSize(ModelConfig.CELL_SIZE_X, ModelConfig.CELL_SIZE_Y, "999");
+
+		BaseConsumpManager baseConsumpManager = new BaseConsumpManager();
+		CalorieManager calorieManager = new CalorieManager();
+		compositeCountryManager = new CompositeCountryManager(baseConsumpManager);
+		lpjYieldReader = new LPJYieldResponseMapReader(desiredProjection);
+		animalRateManager = new AnimalRateManager(compositeCountryManager);
+		
+		if (ModelConfig.DEMAND_FROM_FILE)
+			demandManager = new DemandManagerFromFile(compositeCountryManager,calorieManager);
+		else if (ModelConfig.PRICE_ELASTIC_DEMAND)
+			demandManager = new ElasticDemandManager(ModelConfig.SSP_SCENARIO, baseConsumpManager,calorieManager, compositeCountryManager);
+		else
+			demandManager = new DemandManagerSSP(ModelConfig.SSP_SCENARIO, baseConsumpManager,calorieManager, compositeCountryManager);
+			
+		currentIrrigationData = getFixedIrrigationData();
+		countryBoundaryRaster = getCountryBoundaryRaster();
+		clusterIdRaster = ModelConfig.GENERATE_NEW_YIELD_CLUSTERS ? new RasterSet<IntegerRasterItem>(desiredProjection) : getClusterRaster();
+
+		globalLandUseRaster = new RasterSet<LandUseItem>(desiredProjection);
+		internationalMarket = new InternationalMarket();
+		
+		woodYieldReader = new WoodYieldReader(desiredProjection);
+		carbonFluxReader = new CarbonFluxReader(desiredProjection);
+		
+		createCountryAgents(compositeCountryManager.getAll());
+	}
+
+	/* run the model */
+	private void run() {
+		for (int i = ModelConfig.START_TIMESTEP; i <= ModelConfig.END_TIMESTEP; i++) {
+			Timestep timestep = new Timestep(i);
+			try {
+				doTimestep(timestep);
+			} catch (Exception e) {
+				LpjgOutputer.writeMarkerFile(timestep.getYear(), true);
+				throw new RuntimeException(e);
+			}
+		}
+	}
+	
+	private void doTimestep(Timestep timestep) {
+		System.gc();
+		LogWriter.println("Timestep: " + timestep.toString());
+		LogWriter.println("Memory usage 1: " + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()));
+
+		YieldRaster yieldSurfaces = getYieldSurfaces(timestep); // this will wait for the marker file from LPJ if configured to do so
+		getUpdateIrrigationData(timestep, yieldSurfaces); // updating currentIrrigationData
+
+		// When running half earth we can to alter protected areas data at a point in time
+		if(ModelConfig.HALFEARTH && ModelConfig.FORCE_PROTECTED_AREAS_START_YEAR == timestep.getYear() && !ModelConfig.IS_CALIBRATION_RUN) {
+			new ProtectedAreasReader(globalLandUseRaster).getRasterDataFromFile(ModelConfig.HALF_EARTH_FILE);
+			countryAgents.updateProtectedAreasForAll(globalLandUseRaster);
+		}
+		
+		double carbonDemandIncrease;
+		double carbonDemand;
+		if (ModelConfig.IS_CARBON_ON) {
+			double previousCarbonDemand = (timestep.isInitialTimestep() || ModelConfig.IS_CALIBRATION_RUN ) ? 0: demandManager.getCarbonDemand(timestep.getPreviousTimestep());
+			carbonDemand = demandManager.getCarbonDemand(ModelConfig.IS_CALIBRATION_RUN ? new Timestep(0) : timestep);
+
+			carbonDemandIncrease = (carbonDemand > previousCarbonDemand) ? carbonDemand - previousCarbonDemand: 0;
+		} else {
+			carbonDemandIncrease = 0;
+			carbonDemand = 0;
+		}
+		
+		WoodYieldRasterSet woodYieldData = getWoodYieldData(timestep);
+		CarbonFluxRasterSet carbonFluxData = getCarbonFluxData(timestep);
+
+		Map<LccKey, Double> conversionCosts;
+		conversionCosts = (ModelConfig.CONVERSION_COSTS_FROM_FILE) ? new ConversionCostReader().read() : new ConversionCostReader().calcFromConfig();
+		
+		countryAgents.determineProductionForAll(timestep, yieldSurfaces, currentIrrigationData, carbonFluxData, woodYieldData,
+				conversionCosts, carbonDemandIncrease);
+		internationalMarket.determineInternationalTrade(countryAgents.getAll(), carbonDemand, timestep);
+		
+		int i = 0;		
+		while (i < ModelConfig.DEMAND_RECALC_MAX_ITERATIONS || 
+				(ModelConfig.DEMAND_RECALC_ON_NEGATIVE_STOCK && internationalMarket.negativeStockLevelsExist() && i<10)) { // loop if negative stock have we haven't tried 10 times already
+			LogWriter.println("\n++ Re-estimating prices and demand: timestep " + timestep.getTimestep() + ": interation " + i);
+			countryAgents.recalculateDemandAndNetImportsForAll(); // recalculate demand from new prices and calculate imports and exports
+			internationalMarket.determineInternationalTrade(countryAgents.getAll(), carbonDemand, timestep); // calculate prices
+			i++;
+		}
+		internationalMarket.applyPriceShocks(timestep);
+		
+		LogWriter.println("Memory usage: 2" + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()));
+		
+		// output results
+		outputTimestepResults(timestep);
+		checkAndSaveCheckpoint(timestep);
+
+	}
+	
+	private void checkAndSaveCheckpoint(Timestep timestep) {
+		if (ModelConfig.IS_CALIBRATION_RUN || shouldSaveCheckpoint(timestep)) 
+			serializeCheckpoint();
+	}
+	
+	private boolean shouldSaveCheckpoint(Timestep timestep) {
+		if (ModelConfig.CHECKPOINT_YEARS != null) {
+			LogWriter.println("Looking to see if checkpoint year reached " + ModelConfig.CHECKPOINT_YEARS);
+
+			String[] yearStr = ModelConfig.CHECKPOINT_YEARS.split(",");
+			for(int i=0; i<yearStr.length; i++) {
+				LogWriter.println("Got a checkpoint yearStr " + yearStr[i]);
+				int year = Integer.parseInt(yearStr[i]);
+				if (timestep.getYear() == year)
+					return true;
+			}
+		}
+		return false;
+	}
+
+	private void writeLandCoverFile(Timestep timestep, RasterSet<LandUseItem> landUseRaster) {
+		try {
+			StringBuffer sbHeadings = new StringBuffer("Year,Cropland,Pasture,TimberForest,CarbonForest,Natural,AbPasture,Suitable,EnergyCrop,FertCrop,IrrigCrop");
+			BufferedWriter outputFile = FileWriterHelper.getFileWriter(timestep, ModelConfig.LAND_COVER_OUTPUT_FILE, sbHeadings.toString());
+
+			StringBuffer sbData = new StringBuffer();
+			sbData.append(String.format("%d,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f", timestep.getYear(),
+					LandUseItem.getTotalLandCover(landUseRaster.values(), LandCoverType.CROPLAND),
+					LandUseItem.getTotalLandCover(landUseRaster.values(), LandCoverType.PASTURE),
+					LandUseItem.getTotalLandCover(landUseRaster.values(), LandCoverType.TIMBER_FOREST),
+					LandUseItem.getTotalLandCover(landUseRaster.values(), LandCoverType.CARBON_FOREST),
+					LandUseItem.getTotalLandCover(landUseRaster.values(), LandCoverType.NATURAL),
+					LandUseItem.getAbandonedPasture(landUseRaster.values()),
+					LandUseItem.getSuitableTotal(landUseRaster.values(), timestep.getYear()))
+					);
+						
+			sbData.append(String.format(",%.1f", LandUseItem.getTotalCropArea(landUseRaster.values(), CropType.ENERGY_CROPS)));
+			sbData.append(String.format(",%.1f", LandUseItem.getFertiliserTotal(landUseRaster.values(), CropType.getCropsLessPasture()) / 1000));
+			sbData.append(String.format(",%.1f", LandUseItem.getIrrigationTotal(landUseRaster.values(), CropType.getCropsLessPasture())));
+
+			outputFile.write(sbData.toString());
+			outputFile.newLine();
+			outputFile.close();
+		} catch (IOException e) {
+			LogWriter.print(e);
+		}
+	}
+
+	private void writeGlobalMarketFile(Timestep timestep) {
+		try {
+			BufferedWriter outputFile = FileWriterHelper.getFileWriter(timestep, ModelConfig.PRICES_OUTPUT_FILE, "Year,Crop,Imports (Mt),Exports (Mt),New export price, Stock Levels (Mt)");
+			internationalMarket.writeGlobalMarketFile(timestep, outputFile);
+			outputFile.close();
+		} catch (IOException e) {
+			LogWriter.print(e);
+		}
+	}
+	
+	private void writeGlobalFoodBalanceSheet(Timestep timestep, RasterSet<LandUseItem> landUseRaster) {
+		try {
+			BufferedWriter outputFile = FileWriterHelper.getFileWriter(timestep, ModelConfig.FOOD_BALANCE_SHEET_FILE, "Year,Crop,Production,Imports,Export,TransportLosses,StockVar,Supply,MonogastricsFeed,RuminantsFeed,SeedAndOtherLosses,FoodAnd1stGen,ProdArea");
+			Map<CropType, GlobalPrice> worldPrices = internationalMarket.getWorldPrices();
+			double harvestedAndFallowArea = 0;
+			
+			for (CropType crop : CropType.getCropsLessPasture()) {
+				GlobalPrice priceQuantity = worldPrices.get(crop); // some specific logic for import/exports and this has been aggregated already, so best to use it
+				double prod=0, prodArea=0, feedMonogastrics=0, feedRuminants=0;
+				double exportsBeforeTL=0, imports=0, transportloss=0, stockChange=0;
+				
+				if (priceQuantity != null) {
+					exportsBeforeTL = priceQuantity.getExportsBeforeTransportLoss();
+					imports = priceQuantity.getImportAmount();
+					transportloss = priceQuantity.getTransportLosses();
+					stockChange = priceQuantity.getStockChange();
+				}
+				
+				for (AbstractCountryAgent ca : countryAgents.getAll()) {
+					Map<CropType, CropUsageData> allCropUsage = ca.getCropUsageData();
+					CropUsageData cropUsage = allCropUsage.get(crop);
+					if (cropUsage != null) {
+						prod += cropUsage.getProductionExpected();
+						prodArea += cropUsage.getArea();
+						feedMonogastrics += cropUsage.getMonogastricFeed();
+						feedRuminants += cropUsage.getRuminantFeed();
+					}
+				}
+
+				double seedAndWaste = prod * crop.getSeedAndWasteRate();
+				double netSupply = prod - exportsBeforeTL + imports;
+				double foodAnd1stGen = netSupply - feedMonogastrics - feedRuminants - seedAndWaste;
+				
+				if (!crop.equals(CropType.SETASIDE))
+					prodArea *= (1-ModelConfig.UNHANDLED_CROP_RATE);  // remove unhandled crop area
+				
+				harvestedAndFallowArea += prodArea;
+				
+				StringBuffer sbData = new StringBuffer();
+				sbData.append(String.format("%d,%s", timestep.getYear(), crop.getGamsName()));
+				sbData.append(String.format(",%.2f", prod));
+				sbData.append(String.format(",%.2f,%.2f,%.2f,%.2f", imports, exportsBeforeTL, transportloss, stockChange));
+				sbData.append(String.format(",%.2f,%.2f,%.2f,%.2f,%.2f", netSupply, feedMonogastrics, feedRuminants, seedAndWaste, foodAnd1stGen));
+				sbData.append(String.format(",%.2f", prodArea));
+				outputFile.write(sbData.toString());
+				outputFile.newLine();
+			}
+
+			double cropLandArea = LandUseItem.getTotalLandCover(landUseRaster.values(), LandCoverType.CROPLAND);
+			double unhandledArea = cropLandArea - harvestedAndFallowArea;
+			outputFile.write(String.format("%d,%s,,,,,,,,,,,%.2f", timestep.getYear(), "unhandled", unhandledArea));
+			outputFile.newLine();
+
+			outputFile.close();
+		} catch (IOException e) {
+			LogWriter.print(e);
+		}
+	}
+
+	private void writeDemandFile(Timestep timestep) {
+		try {
+			BufferedWriter outputFile = FileWriterHelper.getFileWriter(timestep, ModelConfig.DEMAND_OUTPUT_FILE, "Year,Commodity,Amount (Mt)");
+
+			for (CommodityType comm : CommodityType.getAllFoodItems()) {
+				double demandAmount = 0;
+
+				for (AbstractCountryAgent country : countryAgents.getAll()) {
+					Map<CommodityType, Double> demands = country.getCurrentProjectedDemand();
+					if (demands == null) {
+						LogWriter.printlnError(country.getCountry() + " " + comm);
+					}
+					
+					Double d = demands.get(comm);
+					if (d != null) {
+						demandAmount += d.doubleValue();
+						LogWriter.println(String.format("%s,%s,%.4f", country.getCountry(), comm.getGamsName(), d));
+					}
+				}
+				StringBuffer sbData = new StringBuffer();
+				sbData.append(String.format("%d,%s", timestep.getYear(), comm.getGamsName()));
+				sbData.append(String.format(",%.1f", demandAmount));
+
+				LogWriter.println("Global demand " + timestep.getYear() + " " + comm.getGamsName() + " " + demandAmount + "\n");
+				outputFile.write(sbData.toString());
+				outputFile.newLine();
+			}
+			
+			double gen2EcDemand = countryAgents.getAll().stream().mapToDouble(c -> c.getCurrentGen2EcDemand()).sum();
+			outputFile.write(String.format("%d,%s,%.1f", timestep.getYear(), CropType.ENERGY_CROPS.getGamsName(), gen2EcDemand));
+			outputFile.newLine();
+			
+			outputFile.close();
+		} catch (IOException e) {
+			LogWriter.print(e);
+		}
+	}
+
+	private void writeDomesticProductionFile(Timestep timestep) {
+		try {
+			StringBuffer sbHeadings = new StringBuffer("Year, Country, Crop, Area, Production, Production_cost, Import_price, Export_price, Consumer_price, Net_imports, Net_import_cost, Prod_shock, Rum_feed_amount, Mon_feed_amount");
+			BufferedWriter outputFile = FileWriterHelper.getFileWriter(timestep, ModelConfig.DOMESTIC_OUTPUT_FILE, sbHeadings.toString());
+
+			for (CropType crop : CropType.getAllItems()) {
+				for (AbstractCountryAgent country : countryAgents.getAll()) {
+					
+					Map<CropType, CropUsageData> cropUsageAllCrops = country.getCropUsageData();
+					CropUsageData cropUsage = cropUsageAllCrops.get(crop);
+					
+					if (cropUsage == null) 
+						continue;
+					
+					double prodCosts = cropUsage.getTotalProdCost();
+					double prod = cropUsage.getProductionExpected();
+					double prodShock = cropUsage.getProductionShock();
+					double area = cropUsage.getArea();
+					double rumFeedAmount = cropUsage.getRuminantFeed();
+					double monFeedAmount = cropUsage.getMonogastricFeed();
+					
+					double importPrice = 0;
+					double exportPrice = 0 ;
+					double consumerPrice = 0;
+					double netImports = 0;
+					double netImportCost = 0;
+
+					if (crop.isImportedCrop()) {
+						CountryPrice px = country.getCurrentCountryPrices().get(crop);
+						importPrice = px.getImportPrice(); 
+						exportPrice = px.getExportPrice();
+						consumerPrice = px.getConsumerPrice();
+						netImports = cropUsage.getNetImportsExpected();	//this isn't accounting for transport losses in exports 
+						netImportCost = cropUsage.getNetImportCostExpected();
+					}
+	
+					StringBuffer sbData = new StringBuffer();
+					sbData.append(String.format("%d,%s,%s", timestep.getYear(), country.getCountry(), crop.getGamsName()));
+					sbData.append(String.format(",%.4f,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f", area, prod, prodCosts, importPrice, exportPrice, consumerPrice, netImports, netImportCost, prodShock, rumFeedAmount, monFeedAmount));
+
+					outputFile.write(sbData.toString());
+					outputFile.newLine();
+				}
+			}
+			outputFile.close();
+
+		} catch (IOException e) {
+			LogWriter.print(e);
+		}
+	}
+	
+	private void writeWoodAndCarbonProdFile(Timestep timestep) {
+		try {
+			StringBuffer sbHeadings = new StringBuffer("Year, Country, Item, Production, Import_price, Export_price, Net_imports,");
+			BufferedWriter outputFile = FileWriterHelper.getFileWriter(timestep, ModelConfig.WOOD_CARBON_OUTPUT_FILE, sbHeadings.toString());
+
+			for (AbstractCountryAgent country : countryAgents.getAll()) {
+
+				Map<WoodType, WoodUsageData> woodUsageMap = country.getWoodUsageData();
+
+				for (WoodType woodType : WoodType.values()) {
+					WoodUsageData woodUsage = woodUsageMap.get(woodType);
+
+					if (woodUsage == null) 
+						continue;
+
+					double prod = woodUsage.getHarvest();
+					double netImports = woodUsage.getNetImport();
+
+					CountryPrice px = country.getCurrentCountryWoodPrice();
+					double importPrice = px.getImportPrice(); 
+					double exportPrice = px.getExportPrice();
+
+					StringBuffer sbData = new StringBuffer();
+					sbData.append(String.format("%d,%s,%s", timestep.getYear(), country.getCountry(), woodType.getName()));
+					sbData.append(String.format(",%.4f,%.4f,%.4f,%.4f", prod, importPrice, exportPrice, netImports));
+
+					outputFile.write(sbData.toString());
+					outputFile.newLine();
+				}
+			}
+			outputFile.close();
+
+		} catch (IOException e) {
+			LogWriter.print(e);
+		}
+	}
+
+	private void writeCountryDemandFile(Timestep timestep){
+
+		try {
+			StringBuffer sbHeadings = new StringBuffer("Year, Country, Commodity, Demand, BioenergyDemand");
+			BufferedWriter outputFile = FileWriterHelper.getFileWriter(timestep, ModelConfig.COUNTRY_DEMAND_FILE, sbHeadings.toString());
+
+			for (AbstractCountryAgent country : countryAgents.getAll()) {
+				for (CommodityType commodity : CommodityType.getAllFoodItems()) {
+					double bioenergyDemand = demandManager.getFirstGenBioenergyDemand(country.getCountry(), timestep.getYear(), commodity);
+					double  demand =  country.getCurrentProjectedDemand().get(commodity);
+
+					StringBuffer sbData = new StringBuffer();
+					sbData.append(String.format("%d,%s,%s", timestep.getYear(), country.getCountry(), commodity.getGamsName()));
+					sbData.append(String.format(",%.3f,%.3f", demand, bioenergyDemand));
+
+					outputFile.write(sbData.toString());
+					outputFile.newLine();
+				}
+			}
+			outputFile.close();
+
+		} catch (IOException e) {
+			LogWriter.print(e);
+		}
+	}
+	
+	private void writeAnimalNumber(Timestep timestep) {
+		try {
+			StringBuffer sbHeadings = new StringBuffer("Year,Country,FAOItem,Heads(M)");
+			BufferedWriter outputFile = FileWriterHelper.getFileWriter(timestep, ModelConfig.ANIMAL_NUMBERS_OUTPUT_FILE, sbHeadings.toString());
+		
+			for (AbstractCountryAgent country : countryAgents.getAll()) {
+				Map<CropType, CropUsageData> cropUsageAllCrops = country.getCropUsageData();
+				for (CropType crop : CropType.getMeatTypes()) {
+					CropUsageData cropusage = cropUsageAllCrops.get(crop);
+					if (cropusage == null)
+						continue;
+					double prod = cropusage.getProductionExpected();
+					
+					Map<String, Double> animalRates = animalRateManager.getAnimalRates(country.getCountry(), crop);
+					for (Entry<String, Double> entry : animalRates.entrySet()) {
+						StringBuffer sbData = new StringBuffer();
+						double animalNum = prod * entry.getValue();
+						sbData.append(String.format("%d,%s,%s,%.4f", timestep.getYear(), country.getCountry(), entry.getKey(), animalNum));		
+						outputFile.write(sbData.toString());
+						outputFile.newLine();
+					}
+			}
+		}
+		outputFile.close();
+			
+		} catch (IOException e) {
+			LogWriter.print(e);
+		}
+	}
+	
+	private void outputTimestepResults(Timestep timestep) {
+
+		writeLandCoverFile(timestep, globalLandUseRaster);
+		writeGlobalMarketFile(timestep);
+		writeDemandFile(timestep);
+		writeDomesticProductionFile(timestep);
+		writeWoodAndCarbonProdFile(timestep);
+		writeCountryDemandFile(timestep);
+		writeGlobalFoodBalanceSheet(timestep, globalLandUseRaster);
+		writeAnimalNumber(timestep);
+
+		if (ModelConfig.OUTPUT_FOR_LPJG) {
+			for (int outputYear : timestep.getYearsFromLast()) {
+				LogWriter.println("Outputing Year: " + outputYear);
+				RasterSet<LandUseItem> landUseToOutput = null;
+
+				if (outputYear == timestep.getYear()) {
+					landUseToOutput = globalLandUseRaster;
+				} 
+				else if (ModelConfig.INTERPOLATE_OUTPUT_YEARS) {
+					// we run with 1 year time steps these days so this code is redundant and flawed
+			/*		InterpolatingRasterSet<LandUseItem> intermediateLandUse = new InterpolatingRasterSet<LandUseItem>( landUseRaster.getHeaderDetails()) {
+						private static final long serialVersionUID = 1306045141011047760L;
+						protected LandUseItem createRasterData() {
+							return new LandUseItem();
+						}
+					};
+					intermediateLandUse.setup(globalLandUseRaster, landUseRaster, timestep.getPreviousTimestep().getYear(), timestep.getYear(), outputYear);
+					landUseToOutput = intermediateLandUse; */
+				}
+
+				if (landUseToOutput != null) {
+					LpjgOutputer lpjOutputer = new LpjgOutputer(outputYear, landUseToOutput);
+					lpjOutputer.writeOutput();
+				}
+			}
+			outputWaterAvailablity(timestep, currentIrrigationData);  // uses the year directory structure created above
+		}
+
+		if (timestep.isInitialTimestep() && ModelConfig.GENERATE_NEW_YIELD_CLUSTERS)
+			outputClusters(clusterIdRaster);
+
+		// Output LandUses to tabular file, for analysis (perhaps)
+		LogWriter.println("Outputing land uses Year: " + timestep.getYear());
+		LandUseOutputer landuseOutputer = new LandUseOutputer(timestep.getYear(), globalLandUseRaster);
+		landuseOutputer.writeOutput();
+
+		// don't really need this a LPJ outputs have same data, although in a slightly different format
+		// outputLandCover(timestep.getYear(), landUseRaster, LandCoverType.CROPLAND);
+		// outputLandCover(timestep.getYear(), landUseRaster, LandCoverType.PASTURE);
+	}
+
+
+	private void outputWaterAvailablity(Timestep timestep, IrrigationRasterSet irrigiationRS) {
+		new RasterOutputer<Double, IrrigationItem>(irrigiationRS, ModelConfig.OUTPUT_DIR + File.separator + timestep.getYear() + File.separator + "IrrigConstraint.asc") {
+			@Override
+			public Double getValue(RasterKey location) {
+				IrrigationItem item = results.get(location);
+				if (item == null)
+					return null;
+
+				return item.getIrrigConstraint();
+			}
+		}.writeOutput();
+	}
+
+	private void outputClusters(RasterSet<IntegerRasterItem> landUseRaster) {
+		new RasterOutputer<Integer, IntegerRasterItem>(landUseRaster, ModelConfig.CLUSTERED_YIELD_FILE) {
+			@Override
+			public Integer getValue(RasterKey location) {
+				IntegerRasterItem item = results.get(location);
+				if (item == null)
+					return null;
+
+				return item.getInt();
+			}
+		}.writeOutput();
+	}
+
+	public RasterSet<IntegerRasterItem> getClusterRaster() {
+		RasterSet<IntegerRasterItem> clusters = new RasterSet<IntegerRasterItem>(desiredProjection) {
+			private static final long serialVersionUID = 2467452274591854417L;
+
+			@Override
+			protected IntegerRasterItem createRasterData() {
+				return new IntegerRasterItem(0);
+			}
+		};
+
+		IntegerRasterReader clusterReader = new IntegerRasterReader(clusters);
+		clusterReader.getRasterDataFromFile(ModelConfig.CLUSTERED_YIELD_FILE);
+		return clusters;
+	}
+
+	public CountryBoundaryRaster getCountryBoundaryRaster() {
+		CountryBoundaryRaster countryBoundaries = new CountryBoundaryRaster(desiredProjection, compositeCountryManager);
+		CountryBoundaryReader countryReader = new CountryBoundaryReader(countryBoundaries);
+		countryReader.getRasterDataFromFile(ModelConfig.COUNTRY_BOUNDARY_FILE);
+		return countryBoundaries;
+	}
+
+	public void createCountryAgents(Collection<CompositeCountry> countryGrouping) {
+		countryAgents = new CountryAgentManager(compositeCountryManager, demandManager, countryBoundaryRaster, internationalMarket, clusterIdRaster, globalLandUseRaster);
+		Map<CompositeCountry, Map<CropType, CropUsageData>> cropUsageDataMap = getInitialCropUsageData();
+		Map<CompositeCountry, Map<WoodType, WoodUsageData>> woodUsageDataMap = getInitialWoodUsageData();
+		RasterSet<LandUseItem> initLU = getInitialLandUse();
+		
+		for (CompositeCountry cc : countryGrouping) {
+			countryAgents.addForCountry(cc, cropUsageDataMap, initLU, woodUsageDataMap);
+			globalLandUseRaster.putAll(initLU);
+		}
+	}
+
+	private RasterSet<LandUseItem> getInitialLandUse() {
+		RasterSet<LandUseItem> initialLU;
+		if (ModelConfig.IS_CALIBRATION_RUN)
+			initialLU = getLandUseFromBaseline();
+		else
+			initialLU = deserializeLandUse();
+
+		return initialLU;
+	}
+	
+	private Map<CompositeCountry, Map<CropType, CropUsageData>> getInitialCropUsageData() {
+		Map<CompositeCountry, Map<CropType, CropUsageData>> cropUsageDataMap;
+		if (ModelConfig.IS_CALIBRATION_RUN)
+			cropUsageDataMap = new CropUsageReader(compositeCountryManager).getCommodityData();
+		else
+			cropUsageDataMap = deserializeCropUsage();
+
+		return cropUsageDataMap;
+	}
+	
+	private Map<CompositeCountry, Map<WoodType, WoodUsageData>> getInitialWoodUsageData() {
+		Map<CompositeCountry, Map<WoodType, WoodUsageData>> woodUsageDataMap;
+		if (ModelConfig.IS_CALIBRATION_RUN) {
+			woodUsageDataMap = new WoodUsageReader(compositeCountryManager).getWoodUsageData();
+
+		} else {
+			woodUsageDataMap = deserializeWoodUsage();
+		}
+		return woodUsageDataMap;
+	}
+	
+	@SuppressWarnings("unchecked")
+	private Map<CompositeCountry, Map<WoodType, WoodUsageData>> deserializeWoodUsage() {
+		try {
+			Map<CompositeCountry, Map<WoodType, WoodUsageData>> woodUsageDataMap;
+			FileInputStream fileIn = new FileInputStream(ModelConfig.SERIALIZED_WOOD_USAGE_FILE);
+			ObjectInputStream in = new ObjectInputStream(fileIn);
+			woodUsageDataMap = (Map<CompositeCountry, Map<WoodType, WoodUsageData>>) in.readObject();
+			in.close();
+			fileIn.close();
+			LogWriter.println("Deserialized " + ModelConfig.SERIALIZED_WOOD_USAGE_FILE);
+			return woodUsageDataMap;
+		} catch (IOException i) {
+			LogWriter.printlnError("Problem deserializing " + ModelConfig.SERIALIZED_WOOD_USAGE_FILE);
+			LogWriter.print(i);
+			return null;
+		} catch (ClassNotFoundException c) {
+			LogWriter.printlnError("Map<CompositeCountry, Double[]> not found");
+			c.printStackTrace();
+			return null;
+		}
+	}
+
+	private void serializeLandUse(RasterSet<LandUseItem> landUseRaster) {
+		RasterSet<LandUseItem> rasterToSerialise = new RasterSet<LandUseItem>(desiredProjection);
+		for (Map.Entry<RasterKey, LandUseItem> entry : landUseRaster.entrySet()) {
+			LandUseItem newLuItem = new LandUseItem(entry.getValue());
+			newLuItem.overwriteLandCoverAreas(new HashMap<LandCoverType, LandCoverTile>()); // Clear data
+			rasterToSerialise.put(entry.getKey(), newLuItem);
+		}
+		try {
+			LogWriter.println("Starting serializing LandUse to " + ModelConfig.SERIALIZED_LAND_USE_FILE);
+			FileOutputStream fileOut = new FileOutputStream(ModelConfig.SERIALIZED_LAND_USE_FILE);
+			ObjectOutputStream out = new ObjectOutputStream(fileOut);
+			out.writeObject(rasterToSerialise);
+			out.close();
+			fileOut.close();
+			LogWriter.println("Serialized data is saved");
+		} catch (IOException i) {
+			i.printStackTrace();
+		}
+
+		LogWriter.println("Starting serializing LandCover to " + ModelConfig.SERIALIZED_LAND_COVER_FILE);
+		LandUseBinarySerializer luSer = new LandUseBinarySerializer();
+		luSer.serializeLandUse(globalLandUseRaster);
+		LogWriter.println("LandCover data is saved");
+	}
+
+	@SuppressWarnings("unchecked")
+	private RasterSet<LandUseItem> deserializeLandUse() {
+		try {
+			RasterSet<LandUseItem> initLU;
+			FileInputStream fileIn = new FileInputStream(ModelConfig.SERIALIZED_LAND_USE_FILE);
+			ObjectInputStream in = new ObjectInputStream(fileIn);
+			initLU = (RasterSet<LandUseItem>) in.readObject();
+			in.close();
+			fileIn.close();
+			LogWriter.println("Deserialized " + ModelConfig.SERIALIZED_LAND_USE_FILE);
+			LandUseBinarySerializer luSer = new LandUseBinarySerializer();
+			luSer.deserializeLandUse(initLU);
+			return initLU;
+		} catch (IOException i) {
+			LogWriter.printlnError("Problem deserializing " + ModelConfig.SERIALIZED_LAND_USE_FILE);
+			LogWriter.print(i);
+			return null;
+		} catch (ClassNotFoundException c) {
+			LogWriter.printlnError("RasterSet<LandUseItem> class not found");
+			c.printStackTrace();
+			return null;
+		}
+	}
+	
+	@SuppressWarnings("unchecked")
+	private Map<CompositeCountry, Map<CropType, CropUsageData>> deserializeCropUsage() {
+		try {
+			Map<CompositeCountry, Map<CropType, CropUsageData>> initCropUsage;
+			FileInputStream fileIn = new FileInputStream(ModelConfig.SERIALIZED_CROP_USAGE_FILE);
+			ObjectInputStream in = new ObjectInputStream(fileIn);
+			initCropUsage = (Map<CompositeCountry, Map<CropType, CropUsageData>>) in.readObject();
+			in.close();
+			fileIn.close();
+			LogWriter.println("Deserialized " + ModelConfig.SERIALIZED_CROP_USAGE_FILE);
+			return initCropUsage;
+		} catch (IOException i) {
+			LogWriter.printlnError("Problem deserializing " + ModelConfig.SERIALIZED_CROP_USAGE_FILE);
+			LogWriter.print(i);
+			return null;
+		} catch (ClassNotFoundException c) {
+			LogWriter.printlnError("Map<CompositeCountry, Map<CropType, CropUsageData>> not found");
+			c.printStackTrace();
+			return null;
+		}
+	}
+	
+
+	/** this is if we are starting from Hurtt of other initial land covers (so we don't have land uses and intensity data) */
+	private RasterSet<LandUseItem> getLandUseFromBaseline() {
+		RasterSet<LandCoverItem> initialLC = new RasterSet<LandCoverItem>(desiredProjection) {
+			private static final long serialVersionUID = 4642550777741425501L;
+
+			protected LandCoverItem createRasterData() {
+				return new LandCoverItem();
+			}
+		};
+
+		new MaxCropAreaReader(initialLC).getRasterDataFromFile(ModelConfig.HIGH_SLOPE_AREAS_FILE); // Fraction unavailable for conversion
+		new LandCoverReader(initialLC).getRasterDataFromFile(ModelConfig.INITAL_LAND_COVER_FILE); // Land cover fractions
+		new LandTileReader(initialLC).getRasterDataFromFile(ModelConfig.LAND_COVER_AGE_DIST_FILENAME); // Age distribution of land cover
+		new InitProtectedAreasReader(initialLC).getRasterDataFromFile(ModelConfig.PROTECTED_AREAS_FILE); // Protected fraction
+
+		RasterSet<LandUseItem> landUseRaster = new RasterSet<LandUseItem>(initialLC.getHeaderDetails());
+		
+		for (Map.Entry<RasterKey, LandCoverItem> entry : initialLC.entrySet()) {
+			//LogWriter.println(initialLC.getXCoordin(entry.getKey()) + " " + initialLC.getYCoordin(entry.getKey()));
+			landUseRaster.put(entry.getKey(), new LandUseItem(entry.getValue()));
+		}
+
+		return landUseRaster;
+	}
+
+	private YieldRaster getYieldSurfaces(Timestep timestep) {
+		return lpjYieldReader.getRasterData(timestep);
+	}
+
+	/** Get irrigation data that does not change with time, should only be called once */
+	private IrrigationRasterSet getFixedIrrigationData() {
+		IrrigationRasterSet fixedIrrigData = new IrrigationRasterSet(desiredProjection, new FPUManager(desiredProjection));
+		new IrrigiationCostReader(fixedIrrigData).getRasterDataFromFile(ModelConfig.IRRIGATION_COST_FILE);
+		new IrrigationConstraintReader(fixedIrrigData).getRasterDataFromFile(ModelConfig.IRRIGATION_CONSTRAINT_FILE);
+		
+		String baseTimestepRootDir = Timestep.getYearSubDir(ModelConfig.YIELD_DIR, ModelConfig.ELLIOTT_BASEYEAR); // needs to be Elliott base timestep
+		new RunOffReader(fixedIrrigData, true).getRasterDataFromFile(baseTimestepRootDir + File.separator + ModelConfig.IRRIG_RUNOFF_FILE);
+		
+		fixedIrrigData.calcIrrigConstraintOffsets();  // should have everything we need to calc offset between Elliott and LPJ data
+		return fixedIrrigData;
+	}
+	
+	/** Get carbon flux data */
+	private CarbonFluxRasterSet getCarbonFluxData(Timestep timestep) {
+		if (ModelConfig.IS_CARBON_ON) {
+			return carbonFluxReader.getCarbonFluxes(globalLandUseRaster, timestep);
+		} else {
+			return new CarbonFluxRasterSet(desiredProjection);
+		}
+	}
+	
+	/** Get wood yield data */
+	private WoodYieldRasterSet getWoodYieldData(Timestep timestep) {
+		if (ModelConfig.IS_FORESTRY_ON) {
+			return woodYieldReader.getWoodYields(globalLandUseRaster, timestep, internationalMarket.getWoodPrice().getExportPrice());	
+		} else {
+			return new WoodYieldRasterSet(desiredProjection);
+		}		
+	}
+
+	/** Ugly in situ update of currentIrrigationData, better if IrrigationRasterSets were handled more immutably */
+	private void getUpdateIrrigationData(Timestep timestep, YieldRaster yieldSurfaces) {
+		String rootDir = timestep.getYearSubDir(ModelConfig.YIELD_DIR);
+		
+		new IrrigationMaxAmountReader(currentIrrigationData).getRasterDataFromFile(rootDir + File.separator + ModelConfig.IRRIG_MAX_WATER_FILENAME);
+
+		if (!ModelConfig.USE_BLUE_WATER_FILE_IRRIG_CONSTRAINT) {
+			new RunOffReader(currentIrrigationData, false).getRasterDataFromFile(rootDir + File.separator + ModelConfig.IRRIG_RUNOFF_FILE);
+			currentIrrigationData.updateIrrigConstraints(timestep);
+		}
+	}
+	
+	private void serializeCheckpoint() {
+		serializeLandUse(globalLandUseRaster);
+		countryAgents.serializeCropUsageForAll();
+		countryAgents.serializeWoodUsageForAll();
+		internationalMarket.serializeGlobalPrices();
+	}
+}
diff --git a/src/ac/ed/lurg/carbon/CarbonFluxItem.java b/src/ac/ed/lurg/carbon/CarbonFluxItem.java
index 25d9abbaf580427775eeea6b35b7534d47d0ab45..ebd8c875c851ae82b79a5ca315a6926fcc7eb7b1 100644
--- a/src/ac/ed/lurg/carbon/CarbonFluxItem.java
+++ b/src/ac/ed/lurg/carbon/CarbonFluxItem.java
@@ -1,81 +1,107 @@
-package ac.ed.lurg.carbon;
-
-import ac.sac.raster.RasterItem;
-
-import java.io.Serializable;
-import java.util.HashMap;
-import java.util.Map;
-
-import ac.ed.lurg.ModelConfig;
-import ac.ed.lurg.Timestep;
-import ac.ed.lurg.landuse.LandCoverTile;
-import ac.ed.lurg.landuse.LccKey;
-import ac.ed.lurg.types.LandCoverType;
-import ac.ed.lurg.types.LandProtectionType;
-
-public class CarbonFluxItem implements RasterItem, Serializable {
-	private static final long serialVersionUID = 440720456140537815L;
-	
-	Map<LccKey, Double> conversionCarbonFlux = new HashMap<LccKey, Double>();
-	Map<LandCoverType, Double> ecosystemCarbonFlux = new HashMap<LandCoverType, Double>();
-	
-	public void setConversionCarbonFlux(LccKey key, double cFlux) {
-		conversionCarbonFlux.put(key, cFlux);
-	}
-	
-	public void calcConversionCarbonFlux(LccKey key, Double[] cFluxes, Map<LandCoverType, LandCoverTile> landUseTiles, Timestep timestep) {	
-		LandCoverTile tiles = landUseTiles.get(key.getFromLc());
-		
-		double totalArea = tiles.getTotalArea(LandProtectionType.CONVERTIBLE);
-		if (totalArea == 0) {
-			this.conversionCarbonFlux.put(key, 0.0);
-		} else {				
-			double totalFlux = 0;
-			for (int age=0; age<=LandCoverTile.getMaxAgeBin(); age++) {
-				int ageCapped = Math.min(age, ModelConfig.CARBON_WOOD_MAX_TIME - 1);
-				totalFlux += cFluxes[ageCapped] * tiles.getArea(LandProtectionType.CONVERTIBLE, age);
-			}
-			double meanFlux = totalFlux / totalArea;
-			this.conversionCarbonFlux.put(key, meanFlux);
-		}
-	}
-	
-	public void setEcosystemCarbonFlux(LandCoverType lcType, double cFlux) {
-		ecosystemCarbonFlux.put(lcType, cFlux);
-	}
-	
-	public void calcEcosystemCarbonFlux(LandCoverType lcType, Double[] cFluxes, Map<LandCoverType, LandCoverTile> landUseTiles, Timestep timestep) {
-		
-		LandCoverTile tiles = landUseTiles.get(lcType);
-		
-		double totalArea = tiles.getTotalArea(LandProtectionType.CONVERTIBLE);
-		if (totalArea == 0) {
-			this.ecosystemCarbonFlux.put(lcType, 0.0);
-		} else {				
-			double totalFlux = 0;
-			for (int age=0; age<=LandCoverTile.getMaxAgeBin(); age++) {
-				int ageCapped = Math.min(age, ModelConfig.CARBON_WOOD_MAX_TIME - 1);
-				totalFlux += cFluxes[ageCapped] * tiles.getArea(LandProtectionType.CONVERTIBLE, age);
-			}
-			double meanFlux = totalFlux / totalArea;
-			this.ecosystemCarbonFlux.put(lcType, meanFlux);
-		}
-	}
-
-	public double getConversionCarbonFlux(LccKey key) {
-		return conversionCarbonFlux.getOrDefault(key, Double.NaN);
-	}
-	
-	public double getEcosystemCarbonFlux(LandCoverType lcType) {
-		return ecosystemCarbonFlux.getOrDefault(lcType, Double.NaN);
-	}
-	
-	public Map<LccKey, Double> getConversionCarbonFluxMap() {
-		return conversionCarbonFlux;
-	}
-	
-	public Map<LandCoverType, Double> getEcosystemCarbonFluxMap() {
-		return ecosystemCarbonFlux;
-	}
-
-}
+package ac.ed.lurg.carbon;
+
+import ac.sac.raster.RasterItem;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import ac.ed.lurg.ModelConfig;
+import ac.ed.lurg.Timestep;
+import ac.ed.lurg.landuse.LandCoverTile;
+import ac.ed.lurg.landuse.LccKey;
+import ac.ed.lurg.types.LandCoverType;
+import ac.ed.lurg.types.LandProtectionType;
+
+public class CarbonFluxItem implements RasterItem, Serializable {
+	private static final long serialVersionUID = 440720456140537815L;
+	
+	Map<LccKey, Double> conversionCarbonFlux = new HashMap<LccKey, Double>();
+	Map<LandCoverType, Double> ecosystemCarbonFlux = new HashMap<LandCoverType, Double>();
+	Map<LandCoverType, Double> carbonHorizonFlux = new HashMap<LandCoverType, Double>();
+	
+	public void setConversionCarbonFlux(LccKey key, double cFlux) {
+		conversionCarbonFlux.put(key, cFlux);
+	}
+	
+	public void calcConversionCarbonFlux(LccKey key, Double[] cFluxes, Map<LandCoverType, LandCoverTile> landUseTiles, Timestep timestep) {	
+		LandCoverTile tiles = landUseTiles.get(key.getFromLc());
+		
+		double totalArea = tiles.getTotalArea(LandProtectionType.CONVERTIBLE);
+		if (totalArea == 0) {
+			this.conversionCarbonFlux.put(key, 0.0);
+		} else {				
+			double totalFlux = 0;
+			for (int age : tiles.getAgeKeys()) {
+				int ageCapped = Math.min(age, ModelConfig.CARBON_WOOD_MAX_TIME - 1);
+				totalFlux += cFluxes[ageCapped] * tiles.getArea(LandProtectionType.CONVERTIBLE, age);
+			}
+			double meanFlux = totalFlux / totalArea;
+			this.conversionCarbonFlux.put(key, meanFlux);
+		}
+	}
+	
+	public void setEcosystemCarbonFlux(LandCoverType lcType, double cFlux) {
+		ecosystemCarbonFlux.put(lcType, cFlux);
+	}
+	
+	public void calcEcosystemCarbonFlux(LandCoverType lcType, Double[] cFluxes, Map<LandCoverType, LandCoverTile> landUseTiles, Timestep timestep) {
+		
+		LandCoverTile tiles = landUseTiles.get(lcType);
+		
+		double totalArea = tiles.getTotalArea(LandProtectionType.CONVERTIBLE);
+		if (totalArea == 0) {
+			this.ecosystemCarbonFlux.put(lcType, 0.0);
+		} else {				
+			double totalFlux = 0;
+			for (int age : tiles.getAgeKeys()) {
+				int ageCapped = Math.min(age, ModelConfig.CARBON_WOOD_MAX_TIME - 1);
+				totalFlux += cFluxes[ageCapped] * tiles.getArea(LandProtectionType.CONVERTIBLE, age);
+			}
+			double meanFlux = totalFlux / totalArea;
+			this.ecosystemCarbonFlux.put(lcType, meanFlux);
+		}
+	}
+	
+	public void calcCarbonHorizonFlux(LandCoverType lcType, Double[] cFluxes) {
+		double totalFlux = 0;
+		for (int i = 0; i < ModelConfig.CARBON_HORIZON; i++) {
+			totalFlux += cFluxes[i];
+		}
+		carbonHorizonFlux.put(lcType, totalFlux);
+	}
+	
+	
+
+	public void setCarbonHorizonFlux(LandCoverType lcType, Double cFlux) {
+		carbonHorizonFlux.put(lcType, cFlux);
+	}
+
+	public double getConversionCarbonFlux(LccKey key) {
+		return conversionCarbonFlux.getOrDefault(key, Double.NaN);
+	}
+	
+	public double getEcosystemCarbonFlux(LandCoverType lcType) {
+		return ecosystemCarbonFlux.getOrDefault(lcType, Double.NaN);
+	}
+	
+	public double getCarbonHorizonFlux(LandCoverType lcType) {
+		return carbonHorizonFlux.getOrDefault(lcType, Double.NaN);
+	}
+	
+	public Map<LandCoverType, Double> getCarbonHorizonFlux() {
+		return carbonHorizonFlux;
+	}
+
+	public Map<LccKey, Double> getConversionCarbonFluxMap() {
+		return conversionCarbonFlux;
+	}
+	
+	public Map<LandCoverType, Double> getEcosystemCarbonFluxMap() {
+		return ecosystemCarbonFlux;
+	}
+	
+	public Map<LandCoverType, Double> getCarbonHorizonFluxMap() {
+		return carbonHorizonFlux;
+	}
+}
diff --git a/src/ac/ed/lurg/carbon/CarbonFluxReader.java b/src/ac/ed/lurg/carbon/CarbonFluxReader.java
index 3f5ed1c3565714cd3d14e3e4f70249b0fa63dad9..ff587e836110b8aa2fcdd9a552497f72c8adeff2 100644
--- a/src/ac/ed/lurg/carbon/CarbonFluxReader.java
+++ b/src/ac/ed/lurg/carbon/CarbonFluxReader.java
@@ -1,107 +1,108 @@
-package ac.ed.lurg.carbon;
-
-import java.io.File;
-import java.util.Map;
-
-import ac.ed.lurg.ModelConfig;
-import ac.ed.lurg.Timestep;
-import ac.ed.lurg.landuse.LandUseItem;
-import ac.ed.lurg.landuse.LccKey;
-import ac.ed.lurg.types.LandCoverType;
-import ac.sac.raster.AbstractBinaryRasterReader;
-import ac.sac.raster.RasterHeaderDetails;
-import ac.sac.raster.RasterKey;
-import ac.sac.raster.RasterSet;
-
-public class CarbonFluxReader {
-	private static final int MIN_COLS = ModelConfig.CARBON_WOOD_MAX_TIME + 2;
-	private static final double CONVERSION_FACTOR = 10.0; // convert kgC/m2 to tC/ha
-	private RasterHeaderDetails rasterProj;
-	private String[] header = new String[MIN_COLS];
-	
-	public CarbonFluxReader(RasterHeaderDetails rasterProj) {
-		this.rasterProj = rasterProj;
-		header[0] = "Lon";
-		header[1] = "Lat";
-		for (int i = 0; i < MIN_COLS - 1; i++) {
-			header[i] = Integer.toString(i);
-		}
-	}
-	
-	public CarbonFluxRasterSet getCarbonFluxes(RasterSet<LandUseItem> landUseRaster, Timestep timestep) {
-		CarbonFluxRasterSet cFluxData = new CarbonFluxRasterSet(rasterProj);
-		
-		getCarbonFluxesConversion("cflux_ntrl_to_past.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.NATURAL, LandCoverType.PASTURE));
-		getCarbonFluxesConversion("cflux_ntrl_to_forC.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.NATURAL, LandCoverType.CARBON_FOREST));
-		getCarbonFluxesConversion("cflux_ntrl_to_forC.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.NATURAL, LandCoverType.TIMBER_FOREST));
-		getCarbonFluxesConversion("cflux_ntrl_to_crop.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.NATURAL, LandCoverType.CROPLAND));
-		
-		getCarbonFluxesConversion("cflux_past_to_ntrl.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.PASTURE, LandCoverType.NATURAL));
-		getCarbonFluxesConversion("cflux_past_to_forC.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.PASTURE, LandCoverType.CARBON_FOREST));
-		getCarbonFluxesConversion("cflux_past_to_forC.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.PASTURE, LandCoverType.TIMBER_FOREST));
-		getCarbonFluxesConversion("cflux_past_to_crop.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.PASTURE, LandCoverType.CROPLAND));
-		
-		getCarbonFluxesConversion("cflux_forC_to_ntrl.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.CARBON_FOREST, LandCoverType.NATURAL));
-		getCarbonFluxesConversion("cflux_forC_to_past.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.CARBON_FOREST, LandCoverType.PASTURE));
-		getCarbonFluxesConversion("cflux_forC_to_forC.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.CARBON_FOREST, LandCoverType.TIMBER_FOREST));
-		getCarbonFluxesConversion("cflux_forC_to_crop.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.CARBON_FOREST, LandCoverType.CROPLAND));
-		
-		getCarbonFluxesConversion("cflux_forC_to_ntrl.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.TIMBER_FOREST, LandCoverType.NATURAL));
-		getCarbonFluxesConversion("cflux_forC_to_past.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.TIMBER_FOREST, LandCoverType.PASTURE));
-		getCarbonFluxesConversion("cflux_forC_to_forC.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.TIMBER_FOREST, LandCoverType.CARBON_FOREST));
-		getCarbonFluxesConversion("cflux_forC_to_crop.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.TIMBER_FOREST, LandCoverType.CROPLAND));
-		
-		getCarbonFluxesConversion("cflux_crop_to_ntrl.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.CROPLAND, LandCoverType.NATURAL));
-		getCarbonFluxesConversion("cflux_crop_to_past.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.CROPLAND, LandCoverType.PASTURE));
-		getCarbonFluxesConversion("cflux_crop_to_forC.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.CROPLAND, LandCoverType.CARBON_FOREST));
-		getCarbonFluxesConversion("cflux_crop_to_forC.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.CROPLAND, LandCoverType.TIMBER_FOREST));
-		
-		getCarbonFluxesEcosystem("cflux_sts_ntrl.dat", cFluxData, landUseRaster, timestep, LandCoverType.NATURAL);
-		getCarbonFluxesEcosystem("cflux_sts_past.dat", cFluxData, landUseRaster, timestep, LandCoverType.PASTURE);
-		getCarbonFluxesEcosystem("cflux_sts_forC.dat", cFluxData, landUseRaster, timestep, LandCoverType.CARBON_FOREST);
-		getCarbonFluxesEcosystem("cflux_sts_forC.dat", cFluxData, landUseRaster, timestep, LandCoverType.TIMBER_FOREST);
-		getCarbonFluxesEcosystem("cflux_sts_crop.dat", cFluxData, landUseRaster, timestep, LandCoverType.CROPLAND);
-		
-		return cFluxData;
-
-	}
-
-	public void getCarbonFluxesConversion(String filename, CarbonFluxRasterSet cFluxData, RasterSet<LandUseItem> landUseRaster, Timestep timestep, LccKey lccKey) {
-
-		AbstractBinaryRasterReader<CarbonFluxItem> cFluxReader = new AbstractBinaryRasterReader<CarbonFluxItem>(header, MIN_COLS - 2, cFluxData) {
-			protected void setData(RasterKey key, CarbonFluxItem item, Map<String, Double> rowValues) {
-
-				Double[] fluxes = getArrayFromRowValues(rowValues);
-				item.calcConversionCarbonFlux(lccKey, fluxes, landUseRaster.get(key).getLandCoverTiles(), timestep);
-			}			
-		};
-	
-		cFluxReader.getRasterDataFromFile(getDataDir(filename, timestep));
-	}
-	
-	public void getCarbonFluxesEcosystem(String filename, CarbonFluxRasterSet cFluxData, RasterSet<LandUseItem> landUseRaster, Timestep timestep, LandCoverType lcType) {
-
-		AbstractBinaryRasterReader<CarbonFluxItem> cFluxReader = new AbstractBinaryRasterReader<CarbonFluxItem>(header, MIN_COLS - 2, cFluxData) {
-			protected void setData(RasterKey key, CarbonFluxItem item, Map<String, Double> rowValues) {
-
-				Double[] fluxes = getArrayFromRowValues(rowValues);
-				item.calcEcosystemCarbonFlux(lcType, fluxes, landUseRaster.get(key).getLandCoverTiles(), timestep);
-			}			
-		};
-	
-		cFluxReader.getRasterDataFromFile(getDataDir(filename, timestep));
-	}
-	
-	private Double[] getArrayFromRowValues(Map<String, Double> rowValues) {
-		int arrSize = rowValues.size();
-		Double[] arr = new Double[arrSize];
-		for (int i=0; i<arrSize; i++) {
-			arr[i] = rowValues.get(Integer.toString(i)) * CONVERSION_FACTOR;
-		}
-		return arr;
-	}
-	
-	private String getDataDir(String filename, Timestep timestep) {
-		return timestep.getWoodAndCarbonYearSubDir(ModelConfig.WOOD_AND_CARBON_DIR) + File.separator + filename;
-	}
-}
+package ac.ed.lurg.carbon;
+
+import java.io.File;
+import java.util.Map;
+
+import ac.ed.lurg.ModelConfig;
+import ac.ed.lurg.Timestep;
+import ac.ed.lurg.landuse.LandUseItem;
+import ac.ed.lurg.landuse.LccKey;
+import ac.ed.lurg.types.LandCoverType;
+import ac.sac.raster.AbstractBinaryRasterReader;
+import ac.sac.raster.RasterHeaderDetails;
+import ac.sac.raster.RasterKey;
+import ac.sac.raster.RasterSet;
+
+public class CarbonFluxReader {
+	private static final int MIN_COLS = ModelConfig.CARBON_WOOD_MAX_TIME + 2;
+	private static final double CONVERSION_FACTOR = 10.0; // convert kgC/m2 to tC/ha
+	private RasterHeaderDetails rasterProj;
+	private String[] header = new String[MIN_COLS];
+	
+	public CarbonFluxReader(RasterHeaderDetails rasterProj) {
+		this.rasterProj = rasterProj;
+		header[0] = "Lon";
+		header[1] = "Lat";
+		for (int i = 0; i < MIN_COLS - 1; i++) {
+			header[i] = Integer.toString(i);
+		}
+	}
+	
+	public CarbonFluxRasterSet getCarbonFluxes(RasterSet<LandUseItem> landUseRaster, Timestep timestep) {
+		CarbonFluxRasterSet cFluxData = new CarbonFluxRasterSet(rasterProj);
+		
+		getCarbonFluxesConversion("cflux_ntrl_to_past.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.NATURAL, LandCoverType.PASTURE));
+		getCarbonFluxesConversion("cflux_ntrl_to_forC.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.NATURAL, LandCoverType.CARBON_FOREST));
+		getCarbonFluxesConversion("cflux_ntrl_to_forC.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.NATURAL, LandCoverType.TIMBER_FOREST));
+		getCarbonFluxesConversion("cflux_ntrl_to_crop.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.NATURAL, LandCoverType.CROPLAND));
+		
+		getCarbonFluxesConversion("cflux_past_to_ntrl.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.PASTURE, LandCoverType.NATURAL));
+		getCarbonFluxesConversion("cflux_past_to_forC.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.PASTURE, LandCoverType.CARBON_FOREST));
+		getCarbonFluxesConversion("cflux_past_to_forC.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.PASTURE, LandCoverType.TIMBER_FOREST));
+		getCarbonFluxesConversion("cflux_past_to_crop.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.PASTURE, LandCoverType.CROPLAND));
+		
+		getCarbonFluxesConversion("cflux_forC_to_ntrl.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.CARBON_FOREST, LandCoverType.NATURAL));
+		getCarbonFluxesConversion("cflux_forC_to_past.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.CARBON_FOREST, LandCoverType.PASTURE));
+		getCarbonFluxesConversion("cflux_forC_to_forC.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.CARBON_FOREST, LandCoverType.TIMBER_FOREST));
+		getCarbonFluxesConversion("cflux_forC_to_crop.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.CARBON_FOREST, LandCoverType.CROPLAND));
+		
+		getCarbonFluxesConversion("cflux_forC_to_ntrl.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.TIMBER_FOREST, LandCoverType.NATURAL));
+		getCarbonFluxesConversion("cflux_forC_to_past.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.TIMBER_FOREST, LandCoverType.PASTURE));
+		getCarbonFluxesConversion("cflux_forC_to_forC.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.TIMBER_FOREST, LandCoverType.CARBON_FOREST));
+		getCarbonFluxesConversion("cflux_forC_to_crop.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.TIMBER_FOREST, LandCoverType.CROPLAND));
+		
+		getCarbonFluxesConversion("cflux_crop_to_ntrl.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.CROPLAND, LandCoverType.NATURAL));
+		getCarbonFluxesConversion("cflux_crop_to_past.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.CROPLAND, LandCoverType.PASTURE));
+		getCarbonFluxesConversion("cflux_crop_to_forC.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.CROPLAND, LandCoverType.CARBON_FOREST));
+		getCarbonFluxesConversion("cflux_crop_to_forC.dat", cFluxData, landUseRaster, timestep, new LccKey(LandCoverType.CROPLAND, LandCoverType.TIMBER_FOREST));
+		
+		getCarbonFluxesEcosystem("cflux_sts_ntrl.dat", cFluxData, landUseRaster, timestep, LandCoverType.NATURAL);
+		getCarbonFluxesEcosystem("cflux_sts_past.dat", cFluxData, landUseRaster, timestep, LandCoverType.PASTURE);
+		getCarbonFluxesEcosystem("cflux_sts_forC.dat", cFluxData, landUseRaster, timestep, LandCoverType.CARBON_FOREST);
+		getCarbonFluxesEcosystem("cflux_sts_forC.dat", cFluxData, landUseRaster, timestep, LandCoverType.TIMBER_FOREST);
+		getCarbonFluxesEcosystem("cflux_sts_crop.dat", cFluxData, landUseRaster, timestep, LandCoverType.CROPLAND);
+		
+		return cFluxData;
+
+	}
+
+	public void getCarbonFluxesConversion(String filename, CarbonFluxRasterSet cFluxData, RasterSet<LandUseItem> landUseRaster, Timestep timestep, LccKey lccKey) {
+
+		AbstractBinaryRasterReader<CarbonFluxItem> cFluxReader = new AbstractBinaryRasterReader<CarbonFluxItem>(header, MIN_COLS - 2, cFluxData) {
+			protected void setData(RasterKey key, CarbonFluxItem item, Map<String, Double> rowValues) {
+
+				Double[] fluxes = getArrayFromRowValues(rowValues);
+				item.calcConversionCarbonFlux(lccKey, fluxes, landUseRaster.get(key).getLandCoverTiles(), timestep);
+			}			
+		};
+	
+		cFluxReader.getRasterDataFromFile(getDataDir(filename, timestep));
+	}
+	
+	public void getCarbonFluxesEcosystem(String filename, CarbonFluxRasterSet cFluxData, RasterSet<LandUseItem> landUseRaster, Timestep timestep, LandCoverType lcType) {
+
+		AbstractBinaryRasterReader<CarbonFluxItem> cFluxReader = new AbstractBinaryRasterReader<CarbonFluxItem>(header, MIN_COLS - 2, cFluxData) {
+			protected void setData(RasterKey key, CarbonFluxItem item, Map<String, Double> rowValues) {
+
+				Double[] fluxes = getArrayFromRowValues(rowValues);
+				item.calcEcosystemCarbonFlux(lcType, fluxes, landUseRaster.get(key).getLandCoverTiles(), timestep);
+				item.calcCarbonHorizonFlux(lcType, fluxes);
+			}			
+		};
+	
+		cFluxReader.getRasterDataFromFile(getDataDir(filename, timestep));
+	}
+	
+	private Double[] getArrayFromRowValues(Map<String, Double> rowValues) {
+		int arrSize = rowValues.size();
+		Double[] arr = new Double[arrSize];
+		for (int i=0; i<arrSize; i++) {
+			arr[i] = rowValues.get(Integer.toString(i)) * CONVERSION_FACTOR;
+		}
+		return arr;
+	}
+	
+	private String getDataDir(String filename, Timestep timestep) {
+		return timestep.getWoodAndCarbonYearSubDir(ModelConfig.WOOD_AND_CARBON_DIR) + File.separator + filename;
+	}
+}
diff --git a/src/ac/ed/lurg/country/AbstractCountryAgent.java b/src/ac/ed/lurg/country/AbstractCountryAgent.java
index 4701863d94358769d6068c98117e9e1a8baea926..f6cc92d43ab593df641048471cb86791e8809970 100644
--- a/src/ac/ed/lurg/country/AbstractCountryAgent.java
+++ b/src/ac/ed/lurg/country/AbstractCountryAgent.java
@@ -1,229 +1,237 @@
-package ac.ed.lurg.country;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import ac.ed.lurg.ModelConfig;
-import ac.ed.lurg.Timestep;
-import ac.ed.lurg.demand.AbstractDemandManager;
-import ac.ed.lurg.landuse.CropUsageData;
-import ac.ed.lurg.landuse.WoodUsageData;
-import ac.ed.lurg.types.CommodityType;
-import ac.ed.lurg.types.CropType;
-import ac.ed.lurg.types.WoodType;
-import ac.ed.lurg.utils.LogWriter;
-
-public abstract class AbstractCountryAgent {
-
-	protected AbstractDemandManager demandManager;
-	protected CompositeCountry country;
-	protected Map<CropType, Double> tradeBarriers;
-	protected Map<CommodityType, Double> currentProjectedDemand;
-	private Map<CropType, GlobalPrice> currentWorldPrices;
-	protected Map<CropType, CountryPrice> currentCountryPrices;
-	private Map<CropType, CountryPrice> previousCountryPrices;
-	protected CountryPrice currentCarbonPrice;
-	protected CountryPrice currentTimberPrice;
-	protected Timestep currentTimestep;
-	protected Map<CommodityType, Map<CropType, Double>> currentDemandFract;
-	protected double currentGen2EcDemand;
-	protected Map<WoodType, Double> currentWoodDemand;
-	protected double exportTaxRate;
-	
-	public AbstractCountryAgent(AbstractDemandManager demandManager,CompositeCountry country, Map<CropType, Double> tradeBarriers) {
-
-		this.demandManager = demandManager;
-		this.country = country;
-		this.tradeBarriers = tradeBarriers;
-	}
-
-	public CompositeCountry getCountry() {
-		return country;
-	}
-	
-	protected void setCurrentTimestep(Timestep timestep) {
-		currentTimestep = timestep;
-	}
-
-	private void calculateCountryPrices(GlobalPrice carbonPrice, GlobalPrice timberPrice){
-		Map<CropType, CountryPrice> countryPrices = new HashMap <CropType, CountryPrice>();
-
-		for (CropType c : CropType.getImportedTypes()) {
-			GlobalPrice worldPrice = currentWorldPrices.get(c);
-			CountryPrice prices = createCountryPrices(c, worldPrice);
-			countryPrices.put(c, prices);
-		}
-				
-		currentCountryPrices = countryPrices;
-		currentCarbonPrice = new CountryPrice(carbonPrice, 0); // no trade barriers
-		currentTimberPrice = new CountryPrice(timberPrice, ModelConfig.WOOD_TRADE_BARRIER);
-	}
-	
-	protected void savePreviousProducerCropPrices() {
-		previousCountryPrices = currentCountryPrices;
-	}
-
-	protected void calculateExportTax() {
-		exportTaxRate = 0;
-		
-		if (previousCountryPrices != null) {
-			for (CropType crop : CommodityType.CEREALS.getCropTypes()) {
-				double newPrice = currentCountryPrices.get(crop).getConsumerPrice();
-				double oldPrice = previousCountryPrices.get(crop).getConsumerPrice();
-				double priceChangeRate = (newPrice - oldPrice)/ oldPrice;
-				if (priceChangeRate > ModelConfig.EXPORT_TAX_THRESHOLD) {
-					exportTaxRate = ModelConfig.EXPORT_TAX_RATE;
-					LogWriter.println(String.format("\ncalculateExportTax: Price Spike, %s, %d, %s, %.6f, %.6f, %.2f\n", country, currentTimestep.getTimestep(), crop, oldPrice, newPrice, priceChangeRate*100));
-					break;
-				}
-				else
-					LogWriter.println(String.format("calculateExportTax: Price change below threshold, %s, %d, %s, %.6f, %.6f, %.2f", country, currentTimestep.getTimestep(), crop, oldPrice, newPrice, priceChangeRate*100));
-			}
-		}
-
-		LogWriter.println(String.format("calculateExportTax %s: exportTax is now %s", country, exportTaxRate));
-	}
-	
-	abstract protected CountryPrice createCountryPrices(CropType crop, GlobalPrice worldPrice);
-	
-	protected void calculateCountryPricesAndDemand(Map<CropType, GlobalPrice> worldPrices, GlobalPrice carbonPrice, GlobalPrice timberPrice, boolean outputGamsDemand) {
-		LogWriter.println("\ncalculateCountryPricesAndDemand for " + country + " "+ currentTimestep.getTimestep());
-		currentWorldPrices = worldPrices;
-		currentDemandFract = getDemandFraction();
-		calculateCountryPrices(carbonPrice, timberPrice);
-		
-		Map<CommodityType, Double> producerPrices = getProducerCommodityPrices();
-		currentProjectedDemand = demandManager.getDemand(country, currentTimestep.getYear(), producerPrices, outputGamsDemand);
-		currentGen2EcDemand = demandManager.getSecondGenBioenergyDemand(country, currentTimestep.getYear());
-		currentWoodDemand = demandManager.getWoodDemandComposite(country, currentTimestep.getYear());
-	}
-	
-	private Map<CommodityType, Double> getProducerCommodityPrices() {
-		if (!ModelConfig.PRICE_ELASTIC_DEMAND)
-			return null;
-						
-		Map<CommodityType, Double> prices = new HashMap<CommodityType, Double>();
-		
-		for (CommodityType commodity : CommodityType.getAllFoodItems()) {
-			double commPricePlum = getCommPriceFromCropPrice(commodity);
-			prices.put(commodity, commPricePlum);
-			LogWriter.println("Producer price for " + commodity.getGamsName() + " is " + commPricePlum);
-		}
-		
-		return prices;
-	}
-	
-	protected abstract double getCommPriceFromCropPrice(CommodityType commodity);
-
-	public Map<CommodityType, Double> getCurrentProjectedDemand() {
-		return currentProjectedDemand;
-	}
-	
-	public Map<CropType, CountryPrice> getCurrentCountryPrices() {
-		return currentCountryPrices;
-	}
-
-	private Map<CommodityType, Map<CropType, Double>> getDemandFraction() {
-		Map<CommodityType, Map<CropType, Double>> baseDemandFact = demandManager.getBaseDemandFracts(country);
-		Map<CommodityType, Map<CropType, Double>> demandFraction = new HashMap<CommodityType, Map<CropType, Double>>();
-
-		for (Map.Entry<CommodityType, Map<CropType, Double>> entry : baseDemandFact.entrySet()) {
-			CommodityType comm = entry.getKey();
-			Map<CropType, Double> baseFracts = entry.getValue();
-			
-			if (ModelConfig.IS_CALIBRATION_RUN || !ModelConfig.DEMAND_FRACT_BY_COST || (comm != CommodityType.CEREALS && comm != CommodityType.OILCROPSPULSES))
-				demandFraction.put(comm, baseFracts);
-			
-			else {
-				Map<CropType, Double> newFracts = new HashMap<CropType, Double>();
-				double totalAdjQuantities = 0;
-
-				for (Map.Entry<CropType, Double> cropEntry : baseFracts.entrySet()) {
-					GlobalPrice worldPrice = currentWorldPrices.get(cropEntry.getKey());
-					double adjFract = worldPrice.getReferencePrice() / worldPrice.getExportPrice() * cropEntry.getValue(); // adjust by price ratio
-					newFracts.put(cropEntry.getKey(), adjFract);
-					totalAdjQuantities += adjFract;
-				}
-
-				Map<CropType, Double> cropFracts = new HashMap<CropType, Double>();
-				for (Map.Entry<CropType, Double> cropEntry : newFracts.entrySet())
-					cropFracts.put(cropEntry.getKey(), cropEntry.getValue()/totalAdjQuantities);  //rebase to 1 by dividing by totalAdjQuantities
-				
-				demandFraction.put(comm, cropFracts);
-			}
-		}
-
-		return demandFraction;
-	}
-
-	protected void updateNetImportsFromProdAndDemand(Map<CommodityType, Double> demands, Map<CommodityType, Map<CropType, Double>>  minDemandFracts, Map<CropType, CropUsageData> cropUsages) {
-		LogWriter.println("AbstractCountryAgent: updateNetImportsFromProdAndDemand for " + country);
-		
-		for (CommodityType commodity : CommodityType.getAllFoodItems()) {
-				
-			if (commodity != CommodityType.CEREALS && commodity != CommodityType.OILCROPSPULSES && commodity.getCropTypes().size() != 1)
-				throw new RuntimeException("Not cereal or oilcropspulses and not 1 to 1 mapping for commodity to crop: " + commodity);  // skips cereals which is a special case and handled separately below
-
-			double demand = demands.get(commodity);
-			if (commodity == CommodityType.CEREALS || commodity == CommodityType.OILCROPSPULSES) {
-				Map<CropType, Double> netImportsFromMinDemands = new HashMap<CropType, Double>();
-				double totalProd = 0, totalmportFromMD = 0, totalExcessProd = 0;
-
-				for (CropType crop : commodity.getCropTypes()) {
-					CropUsageData cropUsage = cropUsages.get(crop);
-					double prod = cropUsage.getProductionExpected() *(1-crop.getSeedAndWasteRate()) - cropUsage.getMonogastricFeed() - cropUsage.getRuminantFeed();
-					totalProd += prod;
-					double minFract = minDemandFracts.get(commodity).containsKey(crop) ? minDemandFracts.get(commodity).get(crop) : 0.0;
-					double netImportsFromMinDemand = minFract * demand - prod;
-					netImportsFromMinDemands.put(crop, netImportsFromMinDemand);
-					
-					if (netImportsFromMinDemand > 0)
-						totalmportFromMD += netImportsFromMinDemand;
-					else
-						totalExcessProd += netImportsFromMinDemand;
-				}
-				double additionalNetImportsRequired = demand - totalProd - totalmportFromMD;
-				LogWriter.println("additionalNetImportsRequired is " + additionalNetImportsRequired);
-
-				for (CropType crop : commodity.getCropTypes()) {
-					double netImportsMD = netImportsFromMinDemands.get(crop);
-					double netImports=0;
-					if (additionalNetImportsRequired > 0) { // overall need to import more
-						if (minDemandFracts.get(commodity).containsKey(crop))
-							netImports = netImportsMD + minDemandFracts.get(commodity).get(crop) * additionalNetImportsRequired; // divide required additional imports by minCerealFracts		
-					}
-					else {  // overall need to export more
-						if (netImportsMD > 0)
-							netImports = netImportsMD; // still import what we need for minimum fraction
-						else if(netImportsMD < 0)
-							netImports = netImportsMD / totalExcessProd * additionalNetImportsRequired; // divide exports by production in excess of minimum demand
-					}
-					
-					CropUsageData cropUsage = cropUsages.get(crop);	
-					LogWriter.println("Updating " + commodity + " net imports " + crop + " to " + netImports);
-					cropUsage.updateNetImports(netImports);
-				}
-			}
-			else {
-				// simple 1-1 commodity to cereal mappings
-				for (CropType crop : commodity.getCropTypes()) {
-					CropUsageData cropUsage = cropUsages.get(crop);
-					double prod =  cropUsage.getProductionExpected() *(1-crop.getSeedAndWasteRate()) - cropUsage.getMonogastricFeed() - cropUsage.getRuminantFeed();
-					double netImports = demand - prod;
-					LogWriter.println("Updating net imports " + crop + " to " + netImports);
-					cropUsage.updateNetImports(netImports);
-				}
-			} 
-		} 
-	}
-	
-	abstract public Map<CropType, CropUsageData> getCropUsageData();
-
-	public double getCurrentGen2EcDemand() {
-		return currentGen2EcDemand;
-	}
-	
-	abstract public double getNetCarbonFlux();
-	
-	abstract public Map<WoodType, WoodUsageData> getWoodUsageData();
+package ac.ed.lurg.country;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import ac.ed.lurg.ModelConfig;
+import ac.ed.lurg.Timestep;
+import ac.ed.lurg.demand.AbstractDemandManager;
+import ac.ed.lurg.landuse.CropUsageData;
+import ac.ed.lurg.landuse.WoodUsageData;
+import ac.ed.lurg.types.CommodityType;
+import ac.ed.lurg.types.CropType;
+import ac.ed.lurg.types.WoodType;
+import ac.ed.lurg.utils.LogWriter;
+
+public abstract class AbstractCountryAgent {
+
+	protected AbstractDemandManager demandManager;
+	protected CompositeCountry country;
+	protected Map<CropType, Double> tradeBarriers;
+	protected Map<CommodityType, Double> currentProjectedDemand;
+	private Map<CropType, GlobalPrice> currentWorldPrices;
+	protected Map<CropType, CountryPrice> currentCountryPrices;
+	private Map<CropType, CountryPrice> previousCountryPrices;
+	protected CountryPrice currentCarbonPrice;
+	protected CountryPrice currentTimberPrice;
+	protected Timestep currentTimestep;
+	protected Map<CommodityType, Map<CropType, Double>> currentDemandFract;
+	protected double currentGen2EcDemand;
+	protected Map<WoodType, Double> currentWoodDemand;
+	protected double exportTaxRate;
+	
+	public AbstractCountryAgent(AbstractDemandManager demandManager,CompositeCountry country, Map<CropType, Double> tradeBarriers) {
+
+		this.demandManager = demandManager;
+		this.country = country;
+		this.tradeBarriers = tradeBarriers;
+	}
+
+	public CompositeCountry getCountry() {
+		return country;
+	}
+	
+	protected void setCurrentTimestep(Timestep timestep) {
+		currentTimestep = timestep;
+	}
+
+	private void calculateCountryPrices(GlobalPrice carbonPrice, GlobalPrice timberPrice){
+		Map<CropType, CountryPrice> countryPrices = new HashMap <CropType, CountryPrice>();
+
+		for (CropType c : CropType.getImportedTypes()) {
+			GlobalPrice worldPrice = currentWorldPrices.get(c);
+			CountryPrice prices = createCountryPrices(c, worldPrice);
+			countryPrices.put(c, prices);
+		}
+				
+		currentCountryPrices = countryPrices;
+		currentCarbonPrice = new CountryPrice(carbonPrice, 0); // no trade barriers
+		currentTimberPrice = new CountryPrice(timberPrice, ModelConfig.WOOD_TRADE_BARRIER);
+	}
+	
+	protected void savePreviousProducerCropPrices() {
+		previousCountryPrices = currentCountryPrices;
+	}
+
+	protected void calculateExportTax() {
+		exportTaxRate = 0;
+		
+		if (previousCountryPrices != null) {
+			for (CropType crop : CommodityType.CEREALS.getCropTypes()) {
+				double newPrice = currentCountryPrices.get(crop).getConsumerPrice();
+				double oldPrice = previousCountryPrices.get(crop).getConsumerPrice();
+				double priceChangeRate = (newPrice - oldPrice)/ oldPrice;
+				if (priceChangeRate > ModelConfig.EXPORT_TAX_THRESHOLD) {
+					exportTaxRate = ModelConfig.EXPORT_TAX_RATE;
+					LogWriter.println(String.format("\ncalculateExportTax: Price Spike, %s, %d, %s, %.6f, %.6f, %.2f\n", country, currentTimestep.getTimestep(), crop, oldPrice, newPrice, priceChangeRate*100));
+					break;
+				}
+				else
+					LogWriter.println(String.format("calculateExportTax: Price change below threshold, %s, %d, %s, %.6f, %.6f, %.2f", country, currentTimestep.getTimestep(), crop, oldPrice, newPrice, priceChangeRate*100));
+			}
+		}
+
+		LogWriter.println(String.format("calculateExportTax %s: exportTax is now %s", country, exportTaxRate));
+	}
+	
+	abstract protected CountryPrice createCountryPrices(CropType crop, GlobalPrice worldPrice);
+	
+	protected void calculateCountryPricesAndDemand(Map<CropType, GlobalPrice> worldPrices, GlobalPrice carbonPrice, GlobalPrice timberPrice, boolean outputGamsDemand) {
+		LogWriter.println("\ncalculateCountryPricesAndDemand for " + country + " "+ currentTimestep.getTimestep());
+		currentWorldPrices = worldPrices;
+		currentDemandFract = getDemandFraction();
+		calculateCountryPrices(carbonPrice, timberPrice);
+		
+		Map<CommodityType, Double> producerPrices = getProducerCommodityPrices();
+		currentProjectedDemand = demandManager.getDemand(country, currentTimestep.getYear(), producerPrices, outputGamsDemand);
+		currentGen2EcDemand = demandManager.getSecondGenBioenergyDemand(country, currentTimestep.getYear());
+		currentWoodDemand = demandManager.getWoodDemandComposite(country, currentTimestep.getYear());
+	}
+	
+	private Map<CommodityType, Double> getProducerCommodityPrices() {
+		if (!ModelConfig.PRICE_ELASTIC_DEMAND)
+			return null;
+						
+		Map<CommodityType, Double> prices = new HashMap<CommodityType, Double>();
+		
+		for (CommodityType commodity : CommodityType.getAllFoodItems()) {
+			double commPricePlum = getCommPriceFromCropPrice(commodity);
+			prices.put(commodity, commPricePlum);
+			LogWriter.println("Producer price for " + commodity.getGamsName() + " is " + commPricePlum);
+		}
+		
+		return prices;
+	}
+	
+	protected abstract double getCommPriceFromCropPrice(CommodityType commodity);
+
+	public Map<CommodityType, Double> getCurrentProjectedDemand() {
+		return currentProjectedDemand;
+	}
+	
+	public Map<CropType, CountryPrice> getCurrentCountryPrices() {
+		return currentCountryPrices;
+	}
+
+	private Map<CommodityType, Map<CropType, Double>> getDemandFraction() {
+		Map<CommodityType, Map<CropType, Double>> baseDemandFact = demandManager.getBaseDemandFracts(country);
+		Map<CommodityType, Map<CropType, Double>> demandFraction = new HashMap<CommodityType, Map<CropType, Double>>();
+
+		for (Map.Entry<CommodityType, Map<CropType, Double>> entry : baseDemandFact.entrySet()) {
+			CommodityType comm = entry.getKey();
+			Map<CropType, Double> baseFracts = entry.getValue();
+			
+			if (ModelConfig.IS_CALIBRATION_RUN || !ModelConfig.DEMAND_FRACT_BY_COST || (comm != CommodityType.CEREALS && comm != CommodityType.OILCROPSPULSES))
+				demandFraction.put(comm, baseFracts);
+			
+			else {
+				Map<CropType, Double> newFracts = new HashMap<CropType, Double>();
+				double totalAdjQuantities = 0;
+
+				for (Map.Entry<CropType, Double> cropEntry : baseFracts.entrySet()) {
+					GlobalPrice worldPrice = currentWorldPrices.get(cropEntry.getKey());
+					double adjFract = worldPrice.getReferencePrice() / worldPrice.getExportPrice() * cropEntry.getValue(); // adjust by price ratio
+					newFracts.put(cropEntry.getKey(), adjFract);
+					totalAdjQuantities += adjFract;
+				}
+
+				Map<CropType, Double> cropFracts = new HashMap<CropType, Double>();
+				for (Map.Entry<CropType, Double> cropEntry : newFracts.entrySet())
+					cropFracts.put(cropEntry.getKey(), cropEntry.getValue()/totalAdjQuantities);  //rebase to 1 by dividing by totalAdjQuantities
+				
+				demandFraction.put(comm, cropFracts);
+			}
+		}
+
+		return demandFraction;
+	}
+
+	protected void updateNetImportsFromProdAndDemand(Map<CommodityType, Double> demands, Map<CommodityType, Map<CropType, Double>>  minDemandFracts, Map<CropType, CropUsageData> cropUsages) {
+		LogWriter.println("AbstractCountryAgent: updateNetImportsFromProdAndDemand for " + country);
+		
+		for (CommodityType commodity : CommodityType.getAllFoodItems()) {
+				
+			if (commodity != CommodityType.CEREALS && commodity != CommodityType.OILCROPSPULSES && commodity.getCropTypes().size() != 1)
+				throw new RuntimeException("Not cereal or oilcropspulses and not 1 to 1 mapping for commodity to crop: " + commodity);  // skips cereals which is a special case and handled separately below
+
+			double demand = demands.get(commodity);
+			if (commodity == CommodityType.CEREALS || commodity == CommodityType.OILCROPSPULSES) {
+				Map<CropType, Double> netImportsFromMinDemands = new HashMap<CropType, Double>();
+				double totalProd = 0, totalmportFromMD = 0, totalExcessProd = 0;
+
+				for (CropType crop : commodity.getCropTypes()) {
+					CropUsageData cropUsage = cropUsages.get(crop);
+					double prod = cropUsage.getProductionExpected() *(1-crop.getSeedAndWasteRate()) - cropUsage.getMonogastricFeed() - cropUsage.getRuminantFeed();
+					totalProd += prod;
+					double minFract = minDemandFracts.get(commodity).containsKey(crop) ? minDemandFracts.get(commodity).get(crop) : 0.0;
+					double netImportsFromMinDemand = minFract * demand - prod;
+					netImportsFromMinDemands.put(crop, netImportsFromMinDemand);
+					
+					if (netImportsFromMinDemand > 0)
+						totalmportFromMD += netImportsFromMinDemand;
+					else
+						totalExcessProd += netImportsFromMinDemand;
+				}
+				double additionalNetImportsRequired = demand - totalProd - totalmportFromMD;
+				LogWriter.println("additionalNetImportsRequired is " + additionalNetImportsRequired);
+
+				for (CropType crop : commodity.getCropTypes()) {
+					double netImportsMD = netImportsFromMinDemands.get(crop);
+					double netImports=0;
+					if (additionalNetImportsRequired > 0) { // overall need to import more
+						if (minDemandFracts.get(commodity).containsKey(crop))
+							netImports = netImportsMD + minDemandFracts.get(commodity).get(crop) * additionalNetImportsRequired; // divide required additional imports by minCerealFracts		
+					}
+					else {  // overall need to export more
+						if (netImportsMD > 0)
+							netImports = netImportsMD; // still import what we need for minimum fraction
+						else if(netImportsMD < 0)
+							netImports = netImportsMD / totalExcessProd * additionalNetImportsRequired; // divide exports by production in excess of minimum demand
+					}
+					
+					CropUsageData cropUsage = cropUsages.get(crop);	
+					LogWriter.println("Updating " + commodity + " net imports " + crop + " to " + netImports);
+					cropUsage.updateNetImports(netImports);
+				}
+			}
+			else {
+				// simple 1-1 commodity to cereal mappings
+				for (CropType crop : commodity.getCropTypes()) {
+					CropUsageData cropUsage = cropUsages.get(crop);
+					double prod =  cropUsage.getProductionExpected() *(1-crop.getSeedAndWasteRate()) - cropUsage.getMonogastricFeed() - cropUsage.getRuminantFeed();
+					double netImports = demand - prod;
+					LogWriter.println("Updating net imports " + crop + " to " + netImports);
+					cropUsage.updateNetImports(netImports);
+				}
+			} 
+		} 
+	}
+	
+	abstract public Map<CropType, CropUsageData> getCropUsageData();
+
+	public double getCurrentGen2EcDemand() {
+		return currentGen2EcDemand;
+	}
+	
+	abstract public double getNetCarbonFlux();
+	
+	abstract public Map<WoodType, WoodUsageData> getWoodUsageData();
+	
+	public CountryPrice getCurrentCountryWoodPrice() {
+		return currentTimberPrice;
+	}
+	
+	public CountryPrice getCurrentCountryCarbonPrice() {
+		return currentCarbonPrice;
+	}
 }
\ No newline at end of file
diff --git a/src/ac/ed/lurg/country/CountryAgent.java b/src/ac/ed/lurg/country/CountryAgent.java
index 09bb7019651787fc3bd508fa7db017dfe685e6b5..ac756610f4120cc9f3197cc6b49d3f224f5f9af4 100644
--- a/src/ac/ed/lurg/country/CountryAgent.java
+++ b/src/ac/ed/lurg/country/CountryAgent.java
@@ -1,352 +1,353 @@
-package ac.ed.lurg.country;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.StandardCopyOption;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import ac.ed.lurg.ModelConfig;
-import ac.ed.lurg.carbon.CarbonFluxItem;
-import ac.ed.lurg.country.gams.GamsCountryInput;
-import ac.ed.lurg.country.gams.GamsRasterInput;
-import ac.ed.lurg.country.gams.GamsRasterOptimiser;
-import ac.ed.lurg.country.gams.GamsRasterOutput;
-import ac.ed.lurg.demand.AbstractDemandManager;
-import ac.ed.lurg.forestry.WoodYieldItem;
-import ac.ed.lurg.landuse.CropUsageData;
-import ac.ed.lurg.landuse.IrrigationItem;
-import ac.ed.lurg.landuse.LandUseItem;
-import ac.ed.lurg.landuse.LccKey;
-import ac.ed.lurg.landuse.WoodUsageData;
-import ac.ed.lurg.types.CommodityType;
-import ac.ed.lurg.types.CropType;
-import ac.ed.lurg.types.LandCoverType;
-import ac.ed.lurg.types.WoodType;
-import ac.ed.lurg.utils.LogWriter;
-import ac.ed.lurg.utils.cluster.Cluster;
-import ac.ed.lurg.utils.cluster.KMeans;
-import ac.ed.lurg.yield.YieldClusterPoint;
-import ac.ed.lurg.yield.YieldRaster;
-import ac.ed.lurg.yield.YieldResponsesItem;
-import ac.sac.raster.IntegerRasterItem;
-import ac.sac.raster.RasterKey;
-import ac.sac.raster.RasterSet;
-
-public class CountryAgent extends AbstractCountryAgent {
-
-	private GamsRasterOutput previousGamsRasterOutput;
-	private RasterSet<IntegerRasterItem> yieldClusters;
-	private Map<CropType, Double> subsidyRates;
-	private boolean saveGamsGdxFiles;
-
-	public CountryAgent(AbstractDemandManager demandManager,CompositeCountry country, RasterSet<LandUseItem> cropAreaRaster,
-			Map<CropType, CropUsageData> cropUsageData, Map<CropType, Double> tradeBarriers, RasterSet<IntegerRasterItem> yieldClusters,
-			Map<CropType, Double> subsidyRates, Map<WoodType, WoodUsageData> countryWoodData) {
-
-		super(demandManager, country, tradeBarriers);
-		this.yieldClusters = yieldClusters;
-		this.subsidyRates = subsidyRates;
-		
-		GamsRasterOutput initialData = new GamsRasterOutput(cropAreaRaster, cropUsageData, countryWoodData);
-		previousGamsRasterOutput = initialData;
-		
-		saveGamsGdxFiles = (ModelConfig.GAMS_COUNTRY_TO_SAVE != null && country.getName().equals(ModelConfig.GAMS_COUNTRY_TO_SAVE));		
-	}
-
-	public RasterSet<IntegerRasterItem> getYieldClusters() {
-		return yieldClusters;
-	}
-
-	private RasterSet<IntegerRasterItem> calcYieldClusters(RasterSet<IrrigationItem> irrigData, YieldRaster countryYieldSurfaces) {
-
-		LogWriter.println("calcYieldClusters: for " + ModelConfig.NUM_YIELD_CLUSTERS + " clusters");	
-		
-		// create collection of ClusteringPoints from countryYieldSurfaces, these have the RasterKey and data for yield (or access to them)
-		HashSet<YieldClusterPoint> clusteringPoints = new HashSet<YieldClusterPoint>();
-		for (Entry<RasterKey, YieldResponsesItem> entry : countryYieldSurfaces.entrySet()) {
-			YieldResponsesItem yieldresp = entry.getValue();
-			if (yieldresp != null) {
-				RasterKey key = entry.getKey();
-				IrrigationItem irrigItem = irrigData.get(key);
-				
-				LandUseItem luItem = previousGamsRasterOutput.getLandUses().get(key);
-				double totalLand = luItem.getTotalLandCoverArea()-luItem.getLandCoverArea(LandCoverType.URBAN);
-				double protectedAreaFrac = (totalLand <= 0) ? 0.0 : luItem.getTotalProtectedArea() / totalLand;
-				
-				clusteringPoints.add(new YieldClusterPoint(key, yieldresp, irrigItem, protectedAreaFrac));
-			}
-		}
-		
-
-		
-		// do the clustering
-		KMeans<String, YieldClusterPoint> kmeans = new KMeans<String, YieldClusterPoint>(clusteringPoints, ModelConfig.NUM_YIELD_CLUSTERS);
-		kmeans.calculateClusters(100, 0.1);
-		kmeans.printClusters();
-
-		// reformat output
-		List<Cluster<String, YieldClusterPoint>> yieldClusters = kmeans.getClusters();
-		RasterSet<IntegerRasterItem> mapping = new RasterSet<IntegerRasterItem>(countryYieldSurfaces.getHeaderDetails());
-		
-		int id = 1;
-		for (Cluster<String, YieldClusterPoint> c : yieldClusters) {
-			for (YieldClusterPoint p : c.getPoints())
-				mapping.put(p.getRasterKey(), new IntegerRasterItem(id));
-
-			if (c.getPoints().size()>0)
-				id++;
-		}
-
-		return mapping;
-	}
-	
-	public GamsRasterOutput determineProduction(YieldRaster countryYieldSurfaces, RasterSet<IrrigationItem> irrigData, 
-			Map<CropType, GlobalPrice> worldPrices, RasterSet<CarbonFluxItem> carbonFluxData, 
-			RasterSet<WoodYieldItem> woodYieldData, Map<LccKey, Double> conversionCosts,
-			double carbonDemandIncrease, GlobalPrice carbonPrice, GlobalPrice woodPrice) {
-
-		// project demand
-		calculateCountryPricesAndDemand(worldPrices, carbonPrice, woodPrice, false);
-		
-		if (ModelConfig.APPLY_EXPORT_TAXES && !ModelConfig.IS_CALIBRATION_RUN) {
-			calculateExportTax();
-			savePreviousProducerCropPrices();
-		}
-		
-		if (saveGamsGdxFiles && ModelConfig.PRICE_ELASTIC_DEMAND)
-			saveGDXFile("demand");
-		
-		if (currentProjectedDemand.size() == 0) {
-			LogWriter.printlnError("No demand for country " + country + " so skipping it");
-		}
-		else if (countryYieldSurfaces.size() == 0 ) {
-			LogWriter.printlnError("No yield values for " + country + " so skipping it");
-		}
-		else if (ModelConfig.DEBUG_JUST_DEMAND_OUTPUT) { // if this debug flag is set we don't do the optimisation
-		}
-		else {			
-			if (yieldClusters==null) {
-				yieldClusters = calcYieldClusters(irrigData, countryYieldSurfaces);  // this should only be on the first timestep
-			}
-			
-			// optimize areas and intensity 
-			GamsRasterInput input = getGamsRasterInput(irrigData, countryYieldSurfaces, woodYieldData, carbonFluxData, conversionCosts, 
-					carbonDemandIncrease);
-			GamsRasterOptimiser opti = new GamsRasterOptimiser(input, yieldClusters);
-			LogWriter.println("Running " + country.getName() + ", currentTimestep " + currentTimestep);
-
-			GamsRasterOutput result = opti.run();
-
-			if (saveGamsGdxFiles)
-				saveGDXFile("landuse");
-
-			previousGamsRasterOutput = result;
-			
-			incrementLandCoverAge();
-
-			return result;
-		}
-
-		throw new RuntimeException("Skipping optimisation of country " + country);
-	}
-	
-	public void recalculateDemandAndNetImports(Map<CropType, GlobalPrice> worldPrices, GlobalPrice carbonPrice, GlobalPrice timberPrice) {
-		shockGDP();
-		calculateCountryPricesAndDemand(worldPrices, carbonPrice, timberPrice, true);
-		updateNetImportsFromProdAndDemand(currentProjectedDemand, currentDemandFract, previousGamsRasterOutput.getCropUsageData());
-	}
-	
-	private void saveGDXFile(String ext) {
-		// some hacky code for debug purposes that keeps each gams gdx file
-		try {
-			Files.copy(
-					FileSystems.getDefault().getPath(ModelConfig.TEMP_DIR + File.separator + "_gams_java_gdb2.gdx"),
-					FileSystems.getDefault().getPath(ModelConfig.TEMP_DIR + File.separator + country.getName().replaceAll("\\s+","") + currentTimestep.getYear() + ext + ".gdx"),
-					StandardCopyOption.REPLACE_EXISTING);
-			Files.copy(
-					FileSystems.getDefault().getPath(ModelConfig.TEMP_DIR + File.separator + "_gams_java_gjo1.lst"),
-					FileSystems.getDefault().getPath(ModelConfig.TEMP_DIR + File.separator + country.getName().replaceAll("\\s+","") + currentTimestep.getYear() + ext + "Listing" + ".lst"),
-					StandardCopyOption.REPLACE_EXISTING);
-		} catch (IOException e) {
-			LogWriter.print(e);
-		}
-	}
-
-	private GamsRasterInput getGamsRasterInput(RasterSet<IrrigationItem> irrigData, YieldRaster countryYieldSurfaces,
-			RasterSet<WoodYieldItem> woodYieldData, RasterSet<CarbonFluxItem> carbonFluxData, Map<LccKey, Double> conversionCosts,
-			double carbonDemandIncrease) {
-		double allowedImportChange;
-
-		if (currentTimestep.isInitialTimestep() || (ModelConfig.IS_CALIBRATION_RUN && currentTimestep.getTimestep() <= ModelConfig.END_FIRST_STAGE_CALIBRATION)) {  // initialisation time-step
-			allowedImportChange = 0.0;
-		}
-		else { // normal (not the initial) time-step
-			allowedImportChange = ModelConfig.MAX_IMPORT_CHANGE;  // when running is calibration model calibrate (ModelConfig.IS_CALIBRATION_RUN==true) MAX_IMPORT_CHANGE is already 0.
-		}
-
-		Map<CropType, TradeConstraint> importConstraints = new HashMap<CropType, TradeConstraint>();
-
-		for (Map.Entry<CropType, CropUsageData> entry : previousGamsRasterOutput.getCropUsageData().entrySet()) {
-			CropUsageData cropUsage = entry.getValue();
-			CropType crop = entry.getKey();
-			double baseTrade = cropUsage.getNetImportsExpected();
-			double changeUp = 0.0;
-			double changeDown = 0.0;
-
-			if (allowedImportChange > 0.0) {
-				double maxOfProdOrSupply = cropUsage.getProductionExpected() + Math.max(baseTrade, 0);  //max of supply for food
-
-				// max of supply overall, needed for when imports are supplying feed and change is zero to allow for production to replace imports
-		// CONFUSING NOT SURE IF NEEDED		if (maxOfProdOrSupply == 0) maxOfProdOrSupply = Math.max(cropUsage.getNetImports() + cropUsage.getProduction() , cropUsage.getProduction());  
-				
-				changeDown = changeUp = allowedImportChange * maxOfProdOrSupply;
-			}
-			
-			if (CropType.ENERGY_CROPS.equals(crop) && baseTrade == 0) { // could apply this logic for all crops?
-				changeDown = changeUp = allowedImportChange * currentGen2EcDemand;
-			}
-
-			importConstraints.put(crop, new TradeConstraint(baseTrade - changeDown, baseTrade + changeUp));
-		}
-		
-		// Carbon import/export constraints TODO not used, might want in future
-		TradeConstraint carbonTradeConstraint;
-		{
-			double baseTrade = getNetCarbonFlux();
-			double countryArea = LandUseItem.getTotalLandArea(previousGamsRasterOutput.getLandUses().values());
-			double change = 0.0;
-			if (allowedImportChange > 0.0)
-				change = Math.max(baseTrade * allowedImportChange, carbonDemandIncrease * countryArea / 1500 * 2);
-			carbonTradeConstraint = new TradeConstraint(baseTrade - change, baseTrade + change);
-		}
-
-		// Timber import/export constraints
-		Map<WoodType, TradeConstraint> woodTradeConstraints = new HashMap<WoodType, TradeConstraint>();
-		
-		for (Map.Entry<WoodType, WoodUsageData> entry : previousGamsRasterOutput.getWoodUsageData().entrySet()) {
-			WoodType woodType = entry.getKey();
-			if (!ModelConfig.IS_FORESTRY_ON) {
-				woodTradeConstraints.put(woodType, new TradeConstraint(0, 0)); // No exports or imports if forestry off
-				continue;
-			}
-			WoodUsageData woodUsage = entry.getValue();
-			double baseTrade = woodUsage.getNetImport();
-			double changeUp = 0.0;
-			double changeDown = 0.0;
-			if (allowedImportChange > 0.0) {
-				double maxOfProdOrSupply = woodUsage.getHarvest() + Math.max(baseTrade, 0);
-				changeUp = changeDown = allowedImportChange * maxOfProdOrSupply;
-			}
-			changeUp = changeDown = currentWoodDemand.values().stream().reduce(0.0, Double::sum); // TODO temp
-			woodTradeConstraints.put(woodType, new TradeConstraint(baseTrade - changeDown, baseTrade + changeUp));
-		}
-
-		GamsCountryInput countryLevelInputs = new GamsCountryInput(country, currentProjectedDemand, currentCountryPrices, importConstraints, 
-				previousGamsRasterOutput.getCropUsageData(), currentDemandFract, subsidyRates, currentCarbonPrice, carbonTradeConstraint, currentWoodDemand, 
-				currentTimberPrice, woodTradeConstraints, previousGamsRasterOutput.getWoodUsageData(), harvestWoodFromRotation(woodYieldData));	
-		GamsRasterInput input = new GamsRasterInput(currentTimestep, countryYieldSurfaces, previousGamsRasterOutput.getLandUses(), irrigData, 
-				woodYieldData, carbonFluxData, conversionCosts, countryLevelInputs);
-
-		return input;
-	}
-	
-	@Override
-	protected double getCommPriceFromCropPrice(CommodityType commodity) {
-		double commPricePlum = 0;
-		Map<CropType, Double> demandFract = currentDemandFract.get(commodity);
-		
-		for (CropType crop : commodity.getCropTypes()) {
-			double consumerCropPrice = currentCountryPrices.get(crop).getConsumerPrice();
-			commPricePlum += consumerCropPrice * demandFract.get(crop);  // weight price by base demand of each cereal crop
-		}
-		
-		return commPricePlum;
-	}
-	
-	@Override
-	protected CountryPrice createCountryPrices(CropType crop, GlobalPrice worldPrice) {
-		Map<CropType, CropUsageData> cropUsageMap = previousGamsRasterOutput.getCropUsageData();
-		CropUsageData cropUsageData = cropUsageMap.get(crop);
-
-		double weighting = Math.max(0, cropUsageData.getShockedNetImports()/(cropUsageData.getProductionExpected()+cropUsageData.getShockedNetImports()));
-		double prodCost = cropUsageData.getProdCostRate();
-		Double tb = tradeBarriers.get(crop);
-		double tradeBarrier = tb==null ? 0 : tb;
-		double exportTaxRateForCrop = CommodityType.CEREALS.getCropTypes().contains(crop) ? exportTaxRate : 0.0;
-		CountryPrice cp = new CountryPrice(crop, worldPrice, tradeBarrier, prodCost, weighting, exportTaxRateForCrop);
-		return cp;
-	}
-
-	public void shockGDP() {
-
-		double totalProduction = getTotalProduction();
-		double shockMagnitude = 0.0;
-
-		for (Map.Entry<CropType, CropUsageData> entry : previousGamsRasterOutput.getCropUsageData().entrySet()) {
-			CropUsageData cropUsage = entry.getValue();
-			CropType crop = entry.getKey();
-			if(crop.isImportedCrop() && cropUsage.getProductionExpected() != 0) 
-				shockMagnitude += (cropUsage.getProductionShock() /cropUsage.getProductionExpected()) * (cropUsage.getProductionExpected()/totalProduction);	
-		}
-		demandManager.updateGdpLossesFromShock(country, currentTimestep.getYear(), shockMagnitude);
-	}
-
-	public RasterSet<LandUseItem> getLandUses() {
-		return previousGamsRasterOutput.getLandUses();
-	}
-
-	public Map<CropType, CropUsageData> getCropUsageData() {
-		return previousGamsRasterOutput.getCropUsageData();
-	}
-	
-	public double getTotalProduction() {
-		double totalProduction = 0;
-		
-		for (Map.Entry<CropType, CropUsageData> entry : previousGamsRasterOutput.getCropUsageData().entrySet()) {
-			CropUsageData cropUsage = entry.getValue();
-			CropType crop = entry.getKey();
-			if(crop.isImportedCrop()) //assuming pasture and set aside not part of agricultural value, valid or not?
-				totalProduction += cropUsage.getProductionExpected();
-		}
-		return totalProduction;
-	}
-	
-	public void updateProtectedAreas(RasterSet<LandUseItem> newProtectedAreas) {
-		for (Entry<RasterKey, LandUseItem> entry : previousGamsRasterOutput.getLandUses().entrySet()) {
-			double updatedProtectedFract = newProtectedAreas.get(entry.getKey()).getProtectedFraction();
-			entry.getValue().setProtectedFraction(updatedProtectedFract);
-		}
-		
-	}
-	
-	private double harvestWoodFromRotation(RasterSet<WoodYieldItem> woodYieldData) {
-		double totalHarvest = 0;
-		for (RasterKey key : previousGamsRasterOutput.getLandUses().keySet()) {
-			WoodYieldItem wyItem = woodYieldData.get(key);
-			if (wyItem == null)
-				continue; // TODO Deal with this properly
-			totalHarvest += wyItem.getCurrentRotationHarvest();
-		}
-		return totalHarvest;
-	}
-	
-	private void incrementLandCoverAge() {
-		for (LandUseItem luItem : previousGamsRasterOutput.getLandUses().values()) {
-			luItem.incrementTilesAge();
-		}
-	}
-	
-	public double getNetCarbonFlux() {
-		return previousGamsRasterOutput.getNetCarbonFlux();
-	}
-	
-	public Map<WoodType, WoodUsageData> getWoodUsageData() {
-		return previousGamsRasterOutput.getWoodUsageData();
-	}
-
+package ac.ed.lurg.country;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import ac.ed.lurg.ModelConfig;
+import ac.ed.lurg.carbon.CarbonFluxItem;
+import ac.ed.lurg.country.gams.GamsCountryInput;
+import ac.ed.lurg.country.gams.GamsRasterInput;
+import ac.ed.lurg.country.gams.GamsRasterOptimiser;
+import ac.ed.lurg.country.gams.GamsRasterOutput;
+import ac.ed.lurg.demand.AbstractDemandManager;
+import ac.ed.lurg.forestry.WoodYieldItem;
+import ac.ed.lurg.landuse.CropUsageData;
+import ac.ed.lurg.landuse.IrrigationItem;
+import ac.ed.lurg.landuse.LandUseItem;
+import ac.ed.lurg.landuse.LccKey;
+import ac.ed.lurg.landuse.WoodUsageData;
+import ac.ed.lurg.types.CommodityType;
+import ac.ed.lurg.types.CropType;
+import ac.ed.lurg.types.LandCoverType;
+import ac.ed.lurg.types.LandProtectionType;
+import ac.ed.lurg.types.WoodType;
+import ac.ed.lurg.utils.LogWriter;
+import ac.ed.lurg.utils.cluster.Cluster;
+import ac.ed.lurg.utils.cluster.KMeans;
+import ac.ed.lurg.yield.YieldClusterPoint;
+import ac.ed.lurg.yield.YieldRaster;
+import ac.ed.lurg.yield.YieldResponsesItem;
+import ac.sac.raster.IntegerRasterItem;
+import ac.sac.raster.RasterKey;
+import ac.sac.raster.RasterSet;
+
+public class CountryAgent extends AbstractCountryAgent {
+
+	private GamsRasterOutput previousGamsRasterOutput;
+	private RasterSet<IntegerRasterItem> yieldClusters;
+	private Map<CropType, Double> subsidyRates;
+	private boolean saveGamsGdxFiles;
+
+	public CountryAgent(AbstractDemandManager demandManager,CompositeCountry country, RasterSet<LandUseItem> cropAreaRaster,
+			Map<CropType, CropUsageData> cropUsageData, Map<CropType, Double> tradeBarriers, RasterSet<IntegerRasterItem> yieldClusters,
+			Map<CropType, Double> subsidyRates, Map<WoodType, WoodUsageData> countryWoodData) {
+
+		super(demandManager, country, tradeBarriers);
+		this.yieldClusters = yieldClusters;
+		this.subsidyRates = subsidyRates;
+		
+		GamsRasterOutput initialData = new GamsRasterOutput(cropAreaRaster, cropUsageData, countryWoodData);
+		previousGamsRasterOutput = initialData;
+		
+		saveGamsGdxFiles = (ModelConfig.GAMS_COUNTRY_TO_SAVE != null && country.getName().equals(ModelConfig.GAMS_COUNTRY_TO_SAVE));
+	}
+
+	public RasterSet<IntegerRasterItem> getYieldClusters() {
+		return yieldClusters;
+	}
+
+	private RasterSet<IntegerRasterItem> calcYieldClusters(RasterSet<IrrigationItem> irrigData, YieldRaster countryYieldSurfaces) {
+
+		LogWriter.println("calcYieldClusters: for " + ModelConfig.NUM_YIELD_CLUSTERS + " clusters");	
+		
+		// create collection of ClusteringPoints from countryYieldSurfaces, these have the RasterKey and data for yield (or access to them)
+		HashSet<YieldClusterPoint> clusteringPoints = new HashSet<YieldClusterPoint>();
+		for (Entry<RasterKey, YieldResponsesItem> entry : countryYieldSurfaces.entrySet()) {
+			YieldResponsesItem yieldresp = entry.getValue();
+			if (yieldresp != null) {
+				RasterKey key = entry.getKey();
+				IrrigationItem irrigItem = irrigData.get(key);
+				
+				LandUseItem luItem = previousGamsRasterOutput.getLandUses().get(key);
+				double totalLand = luItem.getTotalLandCoverArea()-luItem.getLandCoverArea(LandCoverType.URBAN);
+				double protectedAreaFrac = (totalLand <= 0) ? 0.0 : luItem.getTotalLandCoverArea(LandProtectionType.PROTECTED) / totalLand;
+				
+				clusteringPoints.add(new YieldClusterPoint(key, yieldresp, irrigItem, protectedAreaFrac));
+			}
+		}
+		
+
+		
+		// do the clustering
+		KMeans<String, YieldClusterPoint> kmeans = new KMeans<String, YieldClusterPoint>(clusteringPoints, ModelConfig.NUM_YIELD_CLUSTERS);
+		kmeans.calculateClusters(100, 0.1);
+		kmeans.printClusters();
+
+		// reformat output
+		List<Cluster<String, YieldClusterPoint>> yieldClusters = kmeans.getClusters();
+		RasterSet<IntegerRasterItem> mapping = new RasterSet<IntegerRasterItem>(countryYieldSurfaces.getHeaderDetails());
+		
+		int id = 1;
+		for (Cluster<String, YieldClusterPoint> c : yieldClusters) {
+			for (YieldClusterPoint p : c.getPoints())
+				mapping.put(p.getRasterKey(), new IntegerRasterItem(id));
+
+			if (c.getPoints().size()>0)
+				id++;
+		}
+
+		return mapping;
+	}
+	
+	public GamsRasterOutput determineProduction(YieldRaster countryYieldSurfaces, RasterSet<IrrigationItem> irrigData, 
+			Map<CropType, GlobalPrice> worldPrices, RasterSet<CarbonFluxItem> carbonFluxData, 
+			RasterSet<WoodYieldItem> woodYieldData, Map<LccKey, Double> conversionCosts,
+			double carbonDemandIncrease, GlobalPrice carbonPrice, GlobalPrice woodPrice) {
+
+		// project demand
+		calculateCountryPricesAndDemand(worldPrices, carbonPrice, woodPrice, false);
+		
+		if (ModelConfig.APPLY_EXPORT_TAXES && !ModelConfig.IS_CALIBRATION_RUN) {
+			calculateExportTax();
+			savePreviousProducerCropPrices();
+		}
+		
+		if (saveGamsGdxFiles && ModelConfig.PRICE_ELASTIC_DEMAND)
+			saveGDXFile("demand");
+		
+		if (currentProjectedDemand.size() == 0) {
+			LogWriter.printlnError("No demand for country " + country + " so skipping it");
+		}
+		else if (countryYieldSurfaces.size() == 0 ) {
+			LogWriter.printlnError("No yield values for " + country + " so skipping it");
+		}
+		else if (ModelConfig.DEBUG_JUST_DEMAND_OUTPUT) { // if this debug flag is set we don't do the optimisation
+		}
+		else {			
+			if (yieldClusters==null) {
+				yieldClusters = calcYieldClusters(irrigData, countryYieldSurfaces);  // this should only be on the first timestep
+			}
+			
+			// optimize areas and intensity 
+			GamsRasterInput input = getGamsRasterInput(irrigData, countryYieldSurfaces, woodYieldData, carbonFluxData, conversionCosts, 
+					carbonDemandIncrease);
+			GamsRasterOptimiser opti = new GamsRasterOptimiser(input, yieldClusters);
+			LogWriter.println("Running " + country.getName() + ", currentTimestep " + currentTimestep);
+
+			GamsRasterOutput result = opti.run();
+
+			if (saveGamsGdxFiles)
+				saveGDXFile("landuse");
+
+			previousGamsRasterOutput = result;
+			
+			if (!ModelConfig.IS_CALIBRATION_RUN)
+				incrementLandCoverAge();
+
+			return result;
+		}
+
+		throw new RuntimeException("Skipping optimisation of country " + country);
+	}
+	
+	public void recalculateDemandAndNetImports(Map<CropType, GlobalPrice> worldPrices, GlobalPrice carbonPrice, GlobalPrice timberPrice) {
+		shockGDP();
+		calculateCountryPricesAndDemand(worldPrices, carbonPrice, timberPrice, true);
+		updateNetImportsFromProdAndDemand(currentProjectedDemand, currentDemandFract, previousGamsRasterOutput.getCropUsageData());
+	}
+	
+	private void saveGDXFile(String ext) {
+		// some hacky code for debug purposes that keeps each gams gdx file
+		try {
+			Files.copy(
+					FileSystems.getDefault().getPath(ModelConfig.TEMP_DIR + File.separator + "_gams_java_gdb2.gdx"),
+					FileSystems.getDefault().getPath(ModelConfig.TEMP_DIR + File.separator + country.getName().replaceAll("\\s+","") + currentTimestep.getYear() + ext + ".gdx"),
+					StandardCopyOption.REPLACE_EXISTING);
+			Files.copy(
+					FileSystems.getDefault().getPath(ModelConfig.TEMP_DIR + File.separator + "_gams_java_gjo1.lst"),
+					FileSystems.getDefault().getPath(ModelConfig.TEMP_DIR + File.separator + country.getName().replaceAll("\\s+","") + currentTimestep.getYear() + ext + "Listing" + ".lst"),
+					StandardCopyOption.REPLACE_EXISTING);
+		} catch (IOException e) {
+			LogWriter.print(e);
+		}
+	}
+
+	private GamsRasterInput getGamsRasterInput(RasterSet<IrrigationItem> irrigData, YieldRaster countryYieldSurfaces,
+			RasterSet<WoodYieldItem> woodYieldData, RasterSet<CarbonFluxItem> carbonFluxData, Map<LccKey, Double> conversionCosts,
+			double carbonDemandIncrease) {
+		double allowedImportChange;
+
+		if (currentTimestep.isInitialTimestep() || (ModelConfig.IS_CALIBRATION_RUN && currentTimestep.getTimestep() <= ModelConfig.END_FIRST_STAGE_CALIBRATION)) {  // initialisation time-step
+			allowedImportChange = 0.0;
+		}
+		else { // normal (not the initial) time-step
+			allowedImportChange = ModelConfig.MAX_IMPORT_CHANGE;  // when running is calibration model calibrate (ModelConfig.IS_CALIBRATION_RUN==true) MAX_IMPORT_CHANGE is already 0.
+		}
+
+		Map<CropType, TradeConstraint> importConstraints = new HashMap<CropType, TradeConstraint>();
+
+		for (Map.Entry<CropType, CropUsageData> entry : previousGamsRasterOutput.getCropUsageData().entrySet()) {
+			CropUsageData cropUsage = entry.getValue();
+			CropType crop = entry.getKey();
+			double baseTrade = cropUsage.getNetImportsExpected();
+			double changeUp = 0.0;
+			double changeDown = 0.0;
+
+			if (allowedImportChange > 0.0) {
+				double maxOfProdOrSupply = cropUsage.getProductionExpected() + Math.max(baseTrade, 0);  //max of supply for food
+
+				// max of supply overall, needed for when imports are supplying feed and change is zero to allow for production to replace imports
+		// CONFUSING NOT SURE IF NEEDED		if (maxOfProdOrSupply == 0) maxOfProdOrSupply = Math.max(cropUsage.getNetImports() + cropUsage.getProduction() , cropUsage.getProduction());  
+				
+				changeDown = changeUp = allowedImportChange * maxOfProdOrSupply;
+			}
+			
+			if (CropType.ENERGY_CROPS.equals(crop) && baseTrade == 0) { // could apply this logic for all crops?
+				changeDown = changeUp = allowedImportChange * currentGen2EcDemand;
+			}
+
+			importConstraints.put(crop, new TradeConstraint(baseTrade - changeDown, baseTrade + changeUp));
+		}
+		
+		// Carbon import/export constraints TODO not used, might want in future
+		TradeConstraint carbonTradeConstraint;
+		{
+			double baseTrade = getNetCarbonFlux();
+			double countryArea = LandUseItem.getTotalLandArea(previousGamsRasterOutput.getLandUses().values());
+			double change = 0.0;
+			if (allowedImportChange > 0.0)
+				change = Math.max(baseTrade * allowedImportChange, carbonDemandIncrease * countryArea / 1500 * 2);
+			carbonTradeConstraint = new TradeConstraint(baseTrade - change, baseTrade + change);
+		}
+
+		// Timber import/export constraints
+		Map<WoodType, TradeConstraint> woodTradeConstraints = new HashMap<WoodType, TradeConstraint>();
+		
+		for (Map.Entry<WoodType, WoodUsageData> entry : previousGamsRasterOutput.getWoodUsageData().entrySet()) {
+			WoodType woodType = entry.getKey();
+			if (!ModelConfig.IS_FORESTRY_ON) {
+				woodTradeConstraints.put(woodType, new TradeConstraint(0, 0)); // No exports or imports if forestry off
+				continue;
+			}
+			WoodUsageData woodUsage = entry.getValue();
+			
+			double baseTrade = woodUsage.getNetImport();
+			
+			// Make sure country can import sufficient wood
+			if (ModelConfig.IS_CALIBRATION_RUN && currentTimestep.isInitialTimestep()) {
+				// assume 1tC/ha/year
+				double potentialYield = LandUseItem.getTotalLandCover(previousGamsRasterOutput.getLandUses().values(), LandCoverType.TIMBER_FOREST);
+				double currentDemand = currentWoodDemand.get(woodType);
+				double importsNeeded = Math.max(0, currentDemand - potentialYield * 0.5);
+				baseTrade = importsNeeded > 0 ? importsNeeded : baseTrade * 2;
+			}
+			
+			double changeUp = 0.0;
+			double changeDown = 0.0;
+			if (allowedImportChange > 0.0) {
+				double maxOfProdOrSupply = woodUsage.getHarvest() + Math.max(baseTrade, 0);
+				changeUp = changeDown = allowedImportChange * maxOfProdOrSupply;
+			}
+
+			woodTradeConstraints.put(woodType, new TradeConstraint(baseTrade - changeDown, baseTrade + changeUp));
+		}
+
+		GamsCountryInput countryLevelInputs = new GamsCountryInput(country, currentProjectedDemand, currentCountryPrices, importConstraints, 
+				previousGamsRasterOutput.getCropUsageData(), currentDemandFract, subsidyRates, currentCarbonPrice, carbonTradeConstraint, currentWoodDemand, 
+				currentTimberPrice, woodTradeConstraints, previousGamsRasterOutput.getWoodUsageData());	
+		GamsRasterInput input = new GamsRasterInput(currentTimestep, countryYieldSurfaces, previousGamsRasterOutput.getLandUses(), irrigData, 
+				woodYieldData, carbonFluxData, conversionCosts, countryLevelInputs);
+
+		return input;
+	}
+	
+	@Override
+	protected double getCommPriceFromCropPrice(CommodityType commodity) {
+		double commPricePlum = 0;
+		Map<CropType, Double> demandFract = currentDemandFract.get(commodity);
+		
+		for (CropType crop : commodity.getCropTypes()) {
+			double consumerCropPrice = currentCountryPrices.get(crop).getConsumerPrice();
+			commPricePlum += consumerCropPrice * demandFract.get(crop);  // weight price by base demand of each cereal crop
+		}
+		
+		return commPricePlum;
+	}
+	
+	@Override
+	protected CountryPrice createCountryPrices(CropType crop, GlobalPrice worldPrice) {
+		Map<CropType, CropUsageData> cropUsageMap = previousGamsRasterOutput.getCropUsageData();
+		CropUsageData cropUsageData = cropUsageMap.get(crop);
+
+		double weighting = Math.max(0, cropUsageData.getShockedNetImports()/(cropUsageData.getProductionExpected()+cropUsageData.getShockedNetImports()));
+		double prodCost = cropUsageData.getProdCostRate();
+		Double tb = tradeBarriers.get(crop);
+		double tradeBarrier = tb==null ? 0 : tb;
+		double exportTaxRateForCrop = CommodityType.CEREALS.getCropTypes().contains(crop) ? exportTaxRate : 0.0;
+		CountryPrice cp = new CountryPrice(crop, worldPrice, tradeBarrier, prodCost, weighting, exportTaxRateForCrop);
+		return cp;
+	}
+
+	public void shockGDP() {
+
+		double totalProduction = getTotalProduction();
+		double shockMagnitude = 0.0;
+
+		for (Map.Entry<CropType, CropUsageData> entry : previousGamsRasterOutput.getCropUsageData().entrySet()) {
+			CropUsageData cropUsage = entry.getValue();
+			CropType crop = entry.getKey();
+			if(crop.isImportedCrop() && cropUsage.getProductionExpected() != 0) 
+				shockMagnitude += (cropUsage.getProductionShock() /cropUsage.getProductionExpected()) * (cropUsage.getProductionExpected()/totalProduction);	
+		}
+		demandManager.updateGdpLossesFromShock(country, currentTimestep.getYear(), shockMagnitude);
+	}
+
+	public RasterSet<LandUseItem> getLandUses() {
+		return previousGamsRasterOutput.getLandUses();
+	}
+
+	public Map<CropType, CropUsageData> getCropUsageData() {
+		return previousGamsRasterOutput.getCropUsageData();
+	}
+	
+	public double getTotalProduction() {
+		double totalProduction = 0;
+		
+		for (Map.Entry<CropType, CropUsageData> entry : previousGamsRasterOutput.getCropUsageData().entrySet()) {
+			CropUsageData cropUsage = entry.getValue();
+			CropType crop = entry.getKey();
+			if(crop.isImportedCrop()) //assuming pasture and set aside not part of agricultural value, valid or not?
+				totalProduction += cropUsage.getProductionExpected();
+		}
+		return totalProduction;
+	}
+	
+	public void updateProtectedAreas(RasterSet<LandUseItem> newProtectedAreas) {
+		for (Entry<RasterKey, LandUseItem> entry : previousGamsRasterOutput.getLandUses().entrySet()) {
+			double updatedProtectedFract = newProtectedAreas.get(entry.getKey()).getProtectedFraction();
+			entry.getValue().setProtectedFraction(updatedProtectedFract);
+		}
+		
+	}
+
+	private void incrementLandCoverAge() {
+		for (LandUseItem luItem : previousGamsRasterOutput.getLandUses().values()) {
+			luItem.incrementTilesAge();
+		}
+	}
+	
+	public double getNetCarbonFlux() {
+		return previousGamsRasterOutput.getNetCarbonFlux();
+	}
+	
+	public Map<WoodType, WoodUsageData> getWoodUsageData() {
+		return previousGamsRasterOutput.getWoodUsageData();
+	}
 }
\ No newline at end of file
diff --git a/src/ac/ed/lurg/country/gams/GamsCountryInput.java b/src/ac/ed/lurg/country/gams/GamsCountryInput.java
index df876f6966afd77ed5d57319e5c02c76d9e8740d..ac5fb9111b8050736c57e653969f019667779412 100644
--- a/src/ac/ed/lurg/country/gams/GamsCountryInput.java
+++ b/src/ac/ed/lurg/country/gams/GamsCountryInput.java
@@ -1,137 +1,130 @@
-package ac.ed.lurg.country.gams;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import ac.ed.lurg.ModelConfig;
-import ac.ed.lurg.country.CompositeCountry;
-import ac.ed.lurg.country.CountryPrice;
-import ac.ed.lurg.country.TradeConstraint;
-import ac.ed.lurg.landuse.CropUsageData;
-import ac.ed.lurg.landuse.WoodUsageData;
-import ac.ed.lurg.types.CommodityType;
-import ac.ed.lurg.types.CropType;
-import ac.ed.lurg.types.WoodType;
-
-public class GamsCountryInput {
-
-	private CompositeCountry country; // not really required but useful for debugging
-	private Map<CommodityType, Double> projectedDemand;
-	private Map<CropType, TradeConstraint> tradeConstraints;
-	private Map<CropType, CountryPrice> countryPrices;
-	private Map<CropType, CropUsageData> previousCropUsageData;
-	private Map<CommodityType, Map<CropType, Double>> minDemandFractions;
-	private Map<CropType, Double> subsidyRates;
-	private CountryPrice carbonPrice;
-	private TradeConstraint carbonTradeConstraint;
-	private Map<WoodType, Double> woodDemand;
-	private CountryPrice woodPrice;
-	private Map<WoodType, TradeConstraint> woodTradeConstraints;
-	private Map<WoodType, WoodUsageData> previousWoodUsageData;
-	private double woodHarvestFromRotation;
-
-	public GamsCountryInput(CompositeCountry country, Map<CommodityType, Double> projectedDemand, Map<CropType, CountryPrice> countryPrices, 
-			Map<CropType, TradeConstraint> importConstraints, Map<CropType, CropUsageData> previousCropUsageData, 
-			Map<CommodityType, Map<CropType, Double>> minDemandFracts, Map<CropType, Double> subsidyRates,
-			CountryPrice carbonPrice, TradeConstraint carbonTradeConstraint, Map<WoodType, Double> woodDemand, 
-			CountryPrice woodPrice, Map<WoodType, TradeConstraint> woodTradeConstraints, Map<WoodType, WoodUsageData> previousWoodUsageData,
-			double woodHarvestFromRotation) {
-		super();
-		this.country = country;
-		this.projectedDemand = projectedDemand;
-		this.tradeConstraints = importConstraints;
-		this.countryPrices = countryPrices;
-		this.previousCropUsageData = previousCropUsageData;
-		this.minDemandFractions = minDemandFracts;
-		this.subsidyRates = subsidyRates;
-		this.carbonPrice = carbonPrice;
-		this.carbonTradeConstraint = carbonTradeConstraint;
-		this.woodDemand = woodDemand;
-		this.woodPrice = woodPrice;
-		this.woodTradeConstraints = woodTradeConstraints;
-		this.previousWoodUsageData = previousWoodUsageData;
-		this.woodHarvestFromRotation = woodHarvestFromRotation;
-	}
-		
-	public CompositeCountry getCountry() { 
-		return country;
-	}
-	
-	public Map<CommodityType, Double> getProjectedDemand() {
-		return projectedDemand;
-	}
-
-	public Map<CropType, CountryPrice> getCountryPrices() {
-		return countryPrices;
-	}
-	
-	public Map<CropType, TradeConstraint> getTradeConstraints() {
-		return tradeConstraints;
-	}
-	
-	public Map<CropType, CropUsageData> getPreviousCropUsageData() {
-		return previousCropUsageData;
-	}
-
-	public double getMeatEfficiency() {
-		
-		
-		return ModelConfig.MEAT_EFFICIENCY;  // this is already handled by the feed conversion efficiency for each animal product
-	}
-
-	public Map<CropType, Double> getMinTrade() {
-		Map<CropType, Double> netImport = new HashMap<CropType, Double>();
-		for (Map.Entry<CropType, TradeConstraint> entry : tradeConstraints.entrySet()) {
-			netImport.put(entry.getKey(), entry.getValue().getMinConstraint());
-		}
-		return netImport;
-	}
-
-	public Map<CropType, Double> getMaxTrade() {
-		Map<CropType, Double> netImport = new HashMap<CropType, Double>();
-		for (Map.Entry<CropType, TradeConstraint> entry : tradeConstraints.entrySet()) {
-			netImport.put(entry.getKey(), entry.getValue().getMaxConstraint());
-		}
-		return netImport;
-	}
-	
-	public Map<CommodityType, Map<CropType, Double>> getMinDemandFractions() {
-		return minDemandFractions;
-	}
-	
-	public Map<CropType, Double> getSubsidyRates() {
-		return subsidyRates;
-	}
-	
-	public CountryPrice getCarbonPrice() {
-		return carbonPrice;
-	}
-	
-	public TradeConstraint getCarbonTradeConstraint() {
-		return carbonTradeConstraint;
-	}
-
-	public Map<WoodType, Double> getWoodDemand() {
-		return woodDemand;
-	}
-
-	public CountryPrice getWoodPrice() {
-		return woodPrice;
-	}
-
-	public Map<WoodType, TradeConstraint> getWoodTradeConstraints() {
-		return woodTradeConstraints;
-	}
-
-	public Map<WoodType, WoodUsageData> getPreviousWoodUsageData() {
-		return previousWoodUsageData;
-	}
-
-	public double getWoodHarvestFromRotation() {
-		return woodHarvestFromRotation;
-	}
-
-	public void setTradeConstraints(Map<CropType, TradeConstraint> tradeConstraints) {
-		this.tradeConstraints = tradeConstraints;
-	}
+package ac.ed.lurg.country.gams;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import ac.ed.lurg.ModelConfig;
+import ac.ed.lurg.country.CompositeCountry;
+import ac.ed.lurg.country.CountryPrice;
+import ac.ed.lurg.country.TradeConstraint;
+import ac.ed.lurg.landuse.CropUsageData;
+import ac.ed.lurg.landuse.WoodUsageData;
+import ac.ed.lurg.types.CommodityType;
+import ac.ed.lurg.types.CropType;
+import ac.ed.lurg.types.WoodType;
+
+public class GamsCountryInput {
+
+	private CompositeCountry country; // not really required but useful for debugging
+	private Map<CommodityType, Double> projectedDemand;
+	private Map<CropType, TradeConstraint> tradeConstraints;
+	private Map<CropType, CountryPrice> countryPrices;
+	private Map<CropType, CropUsageData> previousCropUsageData;
+	private Map<CommodityType, Map<CropType, Double>> minDemandFractions;
+	private Map<CropType, Double> subsidyRates;
+	private CountryPrice carbonPrice;
+	private TradeConstraint carbonTradeConstraint;
+	private Map<WoodType, Double> woodDemand;
+	private CountryPrice woodPrice;
+	private Map<WoodType, TradeConstraint> woodTradeConstraints;
+	private Map<WoodType, WoodUsageData> previousWoodUsageData;
+
+	public GamsCountryInput(CompositeCountry country, Map<CommodityType, Double> projectedDemand, Map<CropType, CountryPrice> countryPrices, 
+			Map<CropType, TradeConstraint> importConstraints, Map<CropType, CropUsageData> previousCropUsageData, 
+			Map<CommodityType, Map<CropType, Double>> minDemandFracts, Map<CropType, Double> subsidyRates,
+			CountryPrice carbonPrice, TradeConstraint carbonTradeConstraint, Map<WoodType, Double> woodDemand, 
+			CountryPrice woodPrice, Map<WoodType, TradeConstraint> woodTradeConstraints, Map<WoodType, WoodUsageData> previousWoodUsageData) {
+		super();
+		this.country = country;
+		this.projectedDemand = projectedDemand;
+		this.tradeConstraints = importConstraints;
+		this.countryPrices = countryPrices;
+		this.previousCropUsageData = previousCropUsageData;
+		this.minDemandFractions = minDemandFracts;
+		this.subsidyRates = subsidyRates;
+		this.carbonPrice = carbonPrice;
+		this.carbonTradeConstraint = carbonTradeConstraint;
+		this.woodDemand = woodDemand;
+		this.woodPrice = woodPrice;
+		this.woodTradeConstraints = woodTradeConstraints;
+		this.previousWoodUsageData = previousWoodUsageData;
+	}
+		
+	public CompositeCountry getCountry() { 
+		return country;
+	}
+	
+	public Map<CommodityType, Double> getProjectedDemand() {
+		return projectedDemand;
+	}
+
+	public Map<CropType, CountryPrice> getCountryPrices() {
+		return countryPrices;
+	}
+	
+	public Map<CropType, TradeConstraint> getTradeConstraints() {
+		return tradeConstraints;
+	}
+	
+	public Map<CropType, CropUsageData> getPreviousCropUsageData() {
+		return previousCropUsageData;
+	}
+
+	public double getMeatEfficiency() {
+		
+		
+		return ModelConfig.MEAT_EFFICIENCY;  // this is already handled by the feed conversion efficiency for each animal product
+	}
+
+	public Map<CropType, Double> getMinTrade() {
+		Map<CropType, Double> netImport = new HashMap<CropType, Double>();
+		for (Map.Entry<CropType, TradeConstraint> entry : tradeConstraints.entrySet()) {
+			netImport.put(entry.getKey(), entry.getValue().getMinConstraint());
+		}
+		return netImport;
+	}
+
+	public Map<CropType, Double> getMaxTrade() {
+		Map<CropType, Double> netImport = new HashMap<CropType, Double>();
+		for (Map.Entry<CropType, TradeConstraint> entry : tradeConstraints.entrySet()) {
+			netImport.put(entry.getKey(), entry.getValue().getMaxConstraint());
+		}
+		return netImport;
+	}
+	
+	public Map<CommodityType, Map<CropType, Double>> getMinDemandFractions() {
+		return minDemandFractions;
+	}
+	
+	public Map<CropType, Double> getSubsidyRates() {
+		return subsidyRates;
+	}
+	
+	public CountryPrice getCarbonPrice() {
+		return carbonPrice;
+	}
+	
+	public TradeConstraint getCarbonTradeConstraint() {
+		return carbonTradeConstraint;
+	}
+
+	public Map<WoodType, Double> getWoodDemand() {
+		return woodDemand;
+	}
+
+	public CountryPrice getWoodPrice() {
+		return woodPrice;
+	}
+
+	public Map<WoodType, TradeConstraint> getWoodTradeConstraints() {
+		return woodTradeConstraints;
+	}
+
+	public Map<WoodType, WoodUsageData> getPreviousWoodUsageData() {
+		return previousWoodUsageData;
+	}
+
+	public void setTradeConstraints(Map<CropType, TradeConstraint> tradeConstraints) {
+		this.tradeConstraints = tradeConstraints;
+	}
 }
\ No newline at end of file
diff --git a/src/ac/ed/lurg/country/gams/GamsDemandOptimiser.java b/src/ac/ed/lurg/country/gams/GamsDemandOptimiser.java
index bb42fd0030d9517f47a1aabd2e3482085b5e5a80..798fa03d483cf8a61a565da10ca81a9c012d17e9 100755
--- a/src/ac/ed/lurg/country/gams/GamsDemandOptimiser.java
+++ b/src/ac/ed/lurg/country/gams/GamsDemandOptimiser.java
@@ -1,179 +1,179 @@
-package ac.ed.lurg.country.gams;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Vector;
-
-import com.gams.api.GAMSDatabase;
-import com.gams.api.GAMSGlobals;
-import com.gams.api.GAMSJob;
-import com.gams.api.GAMSOptions;
-import com.gams.api.GAMSParameter;
-import com.gams.api.GAMSParameterRecord;
-import com.gams.api.GAMSVariable;
-import com.gams.api.GAMSVariableRecord;
-import com.gams.api.GAMSWorkspace;
-import com.gams.api.GAMSWorkspaceInfo;
-
-import ac.ed.lurg.ModelConfig;
-import ac.ed.lurg.types.CommodityType;
-import ac.ed.lurg.utils.LogWriter;
-
-public class GamsDemandOptimiser {
-
-	private GamsDemandInput inputData;
-
-	public GamsDemandOptimiser(GamsDemandInput inputData) {
-		this.inputData = inputData;
-	}
-	
-	public GamsDemandOutput getDemandPc() {
-		File workingDirectory = new File(ModelConfig.TEMP_DIR);
-		workingDirectory.mkdir();
-		GAMSWorkspaceInfo  wsInfo  = new GAMSWorkspaceInfo();
-		wsInfo.setWorkingDirectory(workingDirectory.getAbsolutePath());
-
-		GAMSWorkspace ws = new GAMSWorkspace(wsInfo);
-		GAMSOptions opt = ws.addOptions();
-		GAMSJob gamsJob = ws.addJobFromFile(ModelConfig.DEMAND_GAMS_MODEL);
-		
-		GAMSDatabase inDB = ws.addDatabase();
-		setupPriceGdpDB(inDB);
-		ArrayList<GAMSDatabase> dbs = new ArrayList<GAMSDatabase>();
-		dbs.add(inDB);
-		opt.defines("gdx_prices_and_gdp", inDB.getName());
-
-		if (ModelConfig.ADJUST_DIET_PREFS) {
-			LogWriter.println("Adusting diet params");
-			GAMSDatabase originalParamDb = ws.addDatabaseFromGDX(new File(ModelConfig.DEMAND_PARAM_FILE).getAbsolutePath());
-			GAMSDatabase adjustedParamDb = ws.addDatabase(originalParamDb);
-			adjustDietParams(adjustedParamDb);
-			dbs.add(adjustedParamDb);
-			opt.defines("gdx_parameters", adjustedParamDb.getName());
-		}
-		else
-			opt.defines("gdx_parameters", new File(ModelConfig.DEMAND_PARAM_FILE).getAbsolutePath());
-
-		long startTime = System.currentTimeMillis();
-		gamsJob.run(opt, dbs.toArray(new GAMSDatabase[dbs.size()]));
-		
-		LogWriter.println("Took " + (System.currentTimeMillis() - startTime) + " ms to run");
-		return handleResults(gamsJob.OutDB());
-	}
-
-	private void adjustDietParams(GAMSDatabase adjustedParamDb) {
-		// get original parameters
-		GAMSParameter tauParam = adjustedParamDb.getParameter("tau");
-		TauCalculationManager tauManager = TauCalculationManager.getInstance();
-		
-		for (GAMSParameterRecord rec : tauParam) {
-			String key = rec.getKeys()[0];
-			double initialTau = rec.getValue();
-			
-			CommodityType commodity = CommodityType.getForGamsName(key);
-			double adjusted = tauManager.getFinalTau(inputData.getYear(), initialTau, commodity);
-			LogWriter.println(String.format("%14s:  initialTau=%.6f, adjusted=%.6f", key, initialTau, adjusted));
-			rec.setValue(adjusted);
-		}
-	}
-
-	private void setupPriceGdpDB(GAMSDatabase inDB) {
-		GAMSParameter gdpPcP = inDB.addParameter("gdp_pc", 0);
-		double gdpPc = inputData.getGdpPc();
-		LogWriter.println(String.format("gdp_pc = %.5f", gdpPc)); 
-		setGamsParamValue(gdpPcP.addRecord(), gdpPc, 5);
-		GamsDemandOutput previousGamsDemands = inputData.getPreviousDemands();
-		
-		GAMSParameter prevUP = inDB.addParameter("previousU", 0);
-		double previousUtility = previousGamsDemands == null ? 0 : previousGamsDemands.getUtility();//-16.3829624069452 + 4.3659621833299 * Math.log10(gdpPc);
-		LogWriter.println(String.format("previousU = %.5f, estimated = %.5f", previousUtility, -16.3829624069452 + 4.3659621833299 * Math.log10(gdpPc))); 
-		setGamsParamValue(prevUP.addRecord(), previousUtility, 5);
-		
-		GAMSParameter maxIncomePropFoodSpendP = inDB.addParameter("maxIncomePropFoodSpend", 0);
-		setGamsParamValue(maxIncomePropFoodSpendP.addRecord(), ModelConfig.MAX_INCOME_PROPORTION_FOOD_SPEND, 4);
-
-		LogWriter.println("\nPrice");	
-		GAMSParameter priceP = inDB.addParameter("price", 1);	
-		GAMSParameter prevSubsP = inDB.addParameter("previousSubs", 1);
-		GAMSParameter prevDiscP = inDB.addParameter("previousDisc", 1);
-
-		for (Map.Entry<CommodityType, Double> entry : inputData.getPrices().entrySet()) {
-			CommodityType comm = entry.getKey();
-			double priceComm = entry.getValue();
-			doCommodity(inDB, priceP, prevSubsP, prevDiscP, gdpPc, previousGamsDemands, comm, priceComm, 
-					comm==CommodityType.NONFOOD ? 5 : 0.05, comm==CommodityType.NONFOOD ? 0.5 : 0.05);
-		}
-	}
-
-	private void doCommodity(GAMSDatabase inDB, GAMSParameter priceP, GAMSParameter prevSubsP, GAMSParameter prevDiscP, double gdpPc, 
-			GamsDemandOutput previousGamsDemands, CommodityType comm, double price, double defaultSubs, double defaultDisc) {
-		
-		Vector<String> v = new Vector<String>();
-		v.add(comm.getGamsName());
-		double prevSubs = defaultSubs, prevDisc = defaultDisc;
-		
-		if (previousGamsDemands != null) {
-			GamsCommodityDemand demand = previousGamsDemands.getGamsDemands(comm);
-			prevSubs = demand.getSubsistence();
-			prevDisc = demand.getDiscretionary();
-		}
-		
-		LogWriter.println(String.format("%14s:  price=%.4f, previousSubs=%.4f, previousDisc=%.4f", comm.getGamsName(), price, prevSubs, prevDisc));
-		setGamsParamValue(priceP.addRecord(v), price, 4);
-		setGamsParamValue(prevSubsP.addRecord(v), prevSubs, 4);
-		setGamsParamValue(prevDiscP.addRecord(v), prevDisc, 4);
-	}
-
-	private double setGamsParamValue(GAMSParameterRecord param, double val, int places) {
-		double dOut = Math.round(val * Math.pow(10,places)) / Math.pow(10,places);
-		param.setValue(dOut);
-		return dOut;
-	}
-		
-	private GamsDemandOutput handleResults(GAMSDatabase outDB) {
-		int modelStatus = (int) outDB.getParameter("ms").findRecord().getValue();
-		String status = GAMSGlobals.ModelStat.lookup(modelStatus).toString();
-		LogWriter.println(String.format("\nDemand %s: Modelstatus (%d) %s, Solvestatus %s", inputData.getCountry(),
-				modelStatus, status, GAMSGlobals.SolveStat.lookup((int) outDB.getParameter("ss").findRecord().getValue()) ));
-		
-		GAMSVariable varU = outDB.getVariable("u");
-		double utility = varU.getFirstRecord().getLevel();
-		LogWriter.println(String.format("u = %.4f", utility)); 
-		
-		GAMSVariable varSubs = outDB.getVariable("SubsistenceC");
-		GAMSVariable varDisc = outDB.getVariable("DiscretionaryC");
-
-		HashSet<GamsCommodityDemand> demandMap = new HashSet<GamsCommodityDemand>();
-		Collection<CommodityType> unhandledComms = new ArrayList<CommodityType>(CommodityType.getAllFoodItems());
-		
-		for (GAMSVariableRecord rec : varSubs) {
-			String commodityName = rec.getKeys()[0];
-			CommodityType commodity = CommodityType.getForGamsName(commodityName, true);
-			
-			if (commodity == null)
-				LogWriter.println(String.format("Can't find commodity from Gams demand, so ignoring: " + commodityName)); 
-			else {
-				double subsGams = rec.getLevel();
-				double discGams = modelStatus==0 ? 0 : varDisc.findRecord(commodityName).getLevel();  //modelStatus==0 is optimiser not run, i.e. too low GDP
-				GamsCommodityDemand demand = new GamsCommodityDemand(commodity, subsGams, discGams,inputData.getKcalPerT(commodity));
-				LogWriter.println(demand.toString());
-				demandMap.add(demand);
-				unhandledComms.remove(commodity);
-			}
-		}
-		
-		for (CommodityType commodity : unhandledComms) {
-			LogWriter.println(commodity + " not found in Gams output so adding with zero demand"); 
-			demandMap.add(new GamsCommodityDemand(commodity, 0, 0, 0));
-		}
-		
-		GAMSParameter parConsumpFact = outDB.getParameter("desiredConsumpFactor");
-		double desiredConsumpFactor = parConsumpFact.getFirstRecord().getValue();
-		LogWriter.println(String.format("desiredConsumpFactor=%.4f", desiredConsumpFactor));
-		
-		return new GamsDemandOutput(status, demandMap, utility, desiredConsumpFactor);
-	}
-}
+package ac.ed.lurg.country.gams;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Vector;
+
+import com.gams.api.GAMSDatabase;
+import com.gams.api.GAMSGlobals;
+import com.gams.api.GAMSJob;
+import com.gams.api.GAMSOptions;
+import com.gams.api.GAMSParameter;
+import com.gams.api.GAMSParameterRecord;
+import com.gams.api.GAMSVariable;
+import com.gams.api.GAMSVariableRecord;
+import com.gams.api.GAMSWorkspace;
+import com.gams.api.GAMSWorkspaceInfo;
+
+import ac.ed.lurg.ModelConfig;
+import ac.ed.lurg.types.CommodityType;
+import ac.ed.lurg.utils.LogWriter;
+
+public class GamsDemandOptimiser {
+
+	private GamsDemandInput inputData;
+
+	public GamsDemandOptimiser(GamsDemandInput inputData) {
+		this.inputData = inputData;
+	}
+	
+	public GamsDemandOutput getDemandPc() {
+		File workingDirectory = new File(ModelConfig.TEMP_DIR);
+		workingDirectory.mkdir();
+		GAMSWorkspaceInfo  wsInfo  = new GAMSWorkspaceInfo();
+		wsInfo.setWorkingDirectory(workingDirectory.getAbsolutePath());
+
+		GAMSWorkspace ws = new GAMSWorkspace(wsInfo);
+		GAMSOptions opt = ws.addOptions();
+		GAMSJob gamsJob = ws.addJobFromFile(ModelConfig.DEMAND_GAMS_MODEL);
+		
+		GAMSDatabase inDB = ws.addDatabase();
+		setupPriceGdpDB(inDB);
+		ArrayList<GAMSDatabase> dbs = new ArrayList<GAMSDatabase>();
+		dbs.add(inDB);
+		opt.defines("gdx_prices_and_gdp", inDB.getName());
+
+		if (ModelConfig.ADJUST_DIET_PREFS) {
+			LogWriter.println("Adusting diet params");
+			GAMSDatabase originalParamDb = ws.addDatabaseFromGDX(new File(ModelConfig.DEMAND_PARAM_FILE).getAbsolutePath());
+			GAMSDatabase adjustedParamDb = ws.addDatabase(originalParamDb);
+			adjustDietParams(adjustedParamDb);
+			dbs.add(adjustedParamDb);
+			opt.defines("gdx_parameters", adjustedParamDb.getName());
+		}
+		else
+			opt.defines("gdx_parameters", new File(ModelConfig.DEMAND_PARAM_FILE).getAbsolutePath());
+
+		long startTime = System.currentTimeMillis();
+		gamsJob.run(opt, dbs.toArray(new GAMSDatabase[dbs.size()]));
+		
+		LogWriter.println("Took " + (System.currentTimeMillis() - startTime) + " ms to run");
+		return handleResults(gamsJob.OutDB());
+	}
+
+	private void adjustDietParams(GAMSDatabase adjustedParamDb) {
+		// get original parameters
+		GAMSParameter tauParam = adjustedParamDb.getParameter("tau");
+		TauCalculationManager tauManager = TauCalculationManager.getInstance();
+		
+		for (GAMSParameterRecord rec : tauParam) {
+			String key = rec.getKeys()[0];
+			double initialTau = rec.getValue();
+			
+			CommodityType commodity = CommodityType.getForGamsName(key);
+			double adjusted = tauManager.getFinalTau(inputData.getYear(), initialTau, commodity);
+			LogWriter.println(String.format("%14s:  initialTau=%.6f, adjusted=%.6f", key, initialTau, adjusted));
+			rec.setValue(adjusted);
+		}
+	}
+
+	private void setupPriceGdpDB(GAMSDatabase inDB) {
+		GAMSParameter gdpPcP = inDB.addParameter("gdp_pc", 0);
+		double gdpPc = inputData.getGdpPc();
+		LogWriter.println(String.format("gdp_pc = %.5f", gdpPc)); 
+		setGamsParamValue(gdpPcP.addRecord(), gdpPc, 5);
+		GamsDemandOutput previousGamsDemands = inputData.getPreviousDemands();
+		
+		GAMSParameter prevUP = inDB.addParameter("previousU", 0);
+		double previousUtility = previousGamsDemands == null ? 0 : previousGamsDemands.getUtility();//-16.3829624069452 + 4.3659621833299 * Math.log10(gdpPc);
+		LogWriter.println(String.format("previousU = %.5f, estimated = %.5f", previousUtility, -16.3829624069452 + 4.3659621833299 * Math.log10(gdpPc))); 
+		setGamsParamValue(prevUP.addRecord(), previousUtility, 5);
+		
+		GAMSParameter maxIncomePropFoodSpendP = inDB.addParameter("maxIncomePropFoodSpend", 0);
+		setGamsParamValue(maxIncomePropFoodSpendP.addRecord(), ModelConfig.MAX_INCOME_PROPORTION_FOOD_SPEND, 4);
+
+		LogWriter.println("\nPrice");	
+		GAMSParameter priceP = inDB.addParameter("price", 1);	
+		GAMSParameter prevSubsP = inDB.addParameter("previousSubs", 1);
+		GAMSParameter prevDiscP = inDB.addParameter("previousDisc", 1);
+
+		for (Map.Entry<CommodityType, Double> entry : inputData.getPrices().entrySet()) {
+			CommodityType comm = entry.getKey();
+			double priceComm = entry.getValue();
+			doCommodity(inDB, priceP, prevSubsP, prevDiscP, gdpPc, previousGamsDemands, comm, priceComm, 
+					comm==CommodityType.NONFOOD ? 5 : 0.05, comm==CommodityType.NONFOOD ? 0.5 : 0.05);
+		}
+	}
+
+	private void doCommodity(GAMSDatabase inDB, GAMSParameter priceP, GAMSParameter prevSubsP, GAMSParameter prevDiscP, double gdpPc, 
+			GamsDemandOutput previousGamsDemands, CommodityType comm, double price, double defaultSubs, double defaultDisc) {
+		
+		Vector<String> v = new Vector<String>();
+		v.add(comm.getGamsName());
+		double prevSubs = defaultSubs, prevDisc = defaultDisc;
+		
+		if (previousGamsDemands != null) {
+			GamsCommodityDemand demand = previousGamsDemands.getGamsDemands(comm);
+			prevSubs = demand.getSubsistence();
+			prevDisc = demand.getDiscretionary();
+		}
+		
+		LogWriter.println(String.format("%14s:  price=%.4f, previousSubs=%.4f, previousDisc=%.4f", comm.getGamsName(), price, prevSubs, prevDisc));
+		setGamsParamValue(priceP.addRecord(v), price, 4);
+		setGamsParamValue(prevSubsP.addRecord(v), prevSubs, 4);
+		setGamsParamValue(prevDiscP.addRecord(v), prevDisc, 4);
+	}
+
+	private double setGamsParamValue(GAMSParameterRecord param, double val, int places) {
+		double dOut = Math.round(val * Math.pow(10,places)) / Math.pow(10,places);
+		param.setValue(dOut);
+		return dOut;
+	}
+		
+	private GamsDemandOutput handleResults(GAMSDatabase outDB) {
+		int modelStatus = (int) outDB.getParameter("ms").findRecord().getValue();
+		String status = GAMSGlobals.ModelStat.lookup(modelStatus).toString();
+		LogWriter.println(String.format("\nDemand %s: Modelstatus (%d) %s, Solvestatus %s", inputData.getCountry(),
+				modelStatus, status, GAMSGlobals.SolveStat.lookup((int) outDB.getParameter("ss").findRecord().getValue()) ));
+		
+		GAMSVariable varU = outDB.getVariable("u");
+		double utility = varU.getFirstRecord().getLevel();
+		LogWriter.println(String.format("u = %.4f", utility)); 
+		
+		GAMSVariable varSubs = outDB.getVariable("SubsistenceC");
+		GAMSVariable varDisc = outDB.getVariable("DiscretionaryC");
+
+		HashSet<GamsCommodityDemand> demandMap = new HashSet<GamsCommodityDemand>();
+		Collection<CommodityType> unhandledComms = new ArrayList<CommodityType>(CommodityType.getAllFoodItems());
+		
+		for (GAMSVariableRecord rec : varSubs) {
+			String commodityName = rec.getKeys()[0];
+			CommodityType commodity = CommodityType.getForGamsName(commodityName, true);
+			
+			if (commodity == null)
+				LogWriter.println(String.format("Can't find commodity from Gams demand, so ignoring: " + commodityName)); 
+			else {
+				double subsGams = rec.getLevel();
+				double discGams = modelStatus==0 ? 0 : varDisc.findRecord(commodityName).getLevel();  //modelStatus==0 is optimiser not run, i.e. too low GDP
+				GamsCommodityDemand demand = new GamsCommodityDemand(commodity, subsGams, discGams,inputData.getKcalPerT(commodity));
+				LogWriter.println(demand.toString());
+				demandMap.add(demand);
+				unhandledComms.remove(commodity);
+			}
+		}
+		
+		for (CommodityType commodity : unhandledComms) {
+			LogWriter.println(commodity + " not found in Gams output so adding with zero demand"); 
+			demandMap.add(new GamsCommodityDemand(commodity, 0, 0, 0));
+		}
+		
+		GAMSParameter parConsumpFact = outDB.getParameter("desiredConsumpFactor");
+		double desiredConsumpFactor = parConsumpFact.getFirstRecord().getValue();
+		LogWriter.println(String.format("desiredConsumpFactor=%.4f", desiredConsumpFactor));
+		
+		return new GamsDemandOutput(status, demandMap, utility, desiredConsumpFactor);
+	}
+}
diff --git a/src/ac/ed/lurg/country/gams/GamsLocationOptimiser.java b/src/ac/ed/lurg/country/gams/GamsLocationOptimiser.java
index 9ee25cda7f762b253831a8cc01eb69939e185aaf..e95480a37db8d26d283a2e1885a18d089d208c7a 100644
--- a/src/ac/ed/lurg/country/gams/GamsLocationOptimiser.java
+++ b/src/ac/ed/lurg/country/gams/GamsLocationOptimiser.java
@@ -1,658 +1,644 @@
-package ac.ed.lurg.country.gams;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Vector;
-
-import com.gams.api.GAMSDatabase;
-import com.gams.api.GAMSException;
-import com.gams.api.GAMSGlobals;
-import com.gams.api.GAMSGlobals.ModelStat;
-import com.gams.api.GAMSJob;
-import com.gams.api.GAMSOptions;
-import com.gams.api.GAMSParameter;
-import com.gams.api.GAMSParameterRecord;
-import com.gams.api.GAMSSet;
-import com.gams.api.GAMSVariable;
-import com.gams.api.GAMSVariableRecord;
-import com.gams.api.GAMSWorkspace;
-import com.gams.api.GAMSWorkspaceInfo;
-
-import ac.ed.lurg.ModelConfig;
-import ac.ed.lurg.carbon.CarbonFluxItem;
-import ac.ed.lurg.country.CountryPrice;
-import ac.ed.lurg.country.LandCoverChangeItem;
-import ac.ed.lurg.country.TradeConstraint;
-import ac.ed.lurg.forestry.WoodYieldData;
-import ac.ed.lurg.landuse.CropUsageData;
-import ac.ed.lurg.landuse.Intensity;
-import ac.ed.lurg.landuse.IrrigationItem;
-import ac.ed.lurg.landuse.LandUseItem;
-import ac.ed.lurg.landuse.LccKey;
-import ac.ed.lurg.landuse.WoodUsageData;
-import ac.ed.lurg.types.CommodityType;
-import ac.ed.lurg.types.CropType;
-import ac.ed.lurg.types.LandCoverType;
-import ac.ed.lurg.types.WoodType;
-import ac.ed.lurg.types.YieldType;
-import ac.ed.lurg.utils.LazyHashMap;
-import ac.ed.lurg.utils.LogWriter;
-import ac.ed.lurg.yield.YieldResponsesItem;
-
-public class GamsLocationOptimiser {
-
-	private static final boolean DEBUG = false;
-
-	private GamsLocationInput inputData;
-
-	public GamsLocationOptimiser(GamsLocationInput inputData) {
-		this.inputData = inputData;
-	}
-
-	public GamsLocationOutput run() {
-
-		File workingDirectory = new File(ModelConfig.TEMP_DIR);
-		workingDirectory.mkdir();
-
-		GAMSWorkspaceInfo  wsInfo  = new GAMSWorkspaceInfo();
-		wsInfo.setWorkingDirectory(workingDirectory.getAbsolutePath());
-		//	wsInfo.setDebugLevel(DebugLevel.VERBOSE);
-
-		GAMSWorkspace ws = new GAMSWorkspace(wsInfo);
-		GAMSDatabase inDB = ws.addDatabase();
-
-		setupInDB(inDB);
-
-		GAMSJob gamsJob = ws.addJobFromFile(ModelConfig.GAMS_MODEL);
-		GAMSOptions opt = ws.addOptions();
-
-		opt.setAllModelTypes("conopt");
-
-		opt.defines("gdxincname", inDB.getName());
-
-		long startTime = System.currentTimeMillis();
-		gamsJob.run(opt, inDB);	
-		
-		if (ModelConfig.CLEANUP_GAMS_DIR) 
-			cleanup(ws.workingDirectory());
-		
-		LogWriter.println("Took " + (System.currentTimeMillis() - startTime) + " ms to run");
-		
-
-		return handleResults(gamsJob.OutDB());
-		
-	}
-
-	private void setupInDB(GAMSDatabase inDB) {
-		//if (DEBUG) LogWriter.println("\nLocation set");
-		GAMSSet locationSet = inDB.addSet("location", 1);
-		for (Integer locId : inputData.getPreviousLandUse().keySet()) {
-			//if (DEBUG) LogWriter.println("     " + locId);
-			locationSet.addRecord(locId.toString());
-		}
-
-		if (DEBUG) LogWriter.println("\nPrevious crop and land areas");	
-		GAMSParameter prevCropP = inDB.addParameter("previousCropArea", 2);
-		GAMSParameter prevFertIP = inDB.addParameter("previousFertIntensity", 2);
-		GAMSParameter prevIrrigIP = inDB.addParameter("previousIrrigIntensity", 2);
-		GAMSParameter prevOtherIP = inDB.addParameter("previousOtherIntensity", 2);
-		
-		GAMSParameter previousRuminantFeedP = inDB.addParameter("previousRuminantFeed", 1);
-		GAMSParameter previousMonogastricFeedP = inDB.addParameter("previousMonogastricFeed", 1);
-		GAMSParameter previousImportAmountP = inDB.addParameter("previousImportAmount", 1);
-		GAMSParameter previousExportAmountP = inDB.addParameter("previousExportAmount", 1);
-	
-		GAMSParameter landP = inDB.addParameter("suitableLandArea", 1);
-		GAMSParameter seedAndWasteRateP = inDB.addParameter("seedAndWasteRate", 1);	
-		
-		GAMSParameter prevLandCoverP = inDB.addParameter("previousLandCoverArea", 2);
-		GAMSParameter infrastructureCostP = inDB.addParameter("infrastructureCost", 1);
-		
-		double totalAgriLand = 0;
-		double totalSuitable = 0;
-
-		for (Map.Entry<Integer, ? extends LandUseItem> entry : inputData.getPreviousLandUse().entrySet()) {
-			Integer locationId = entry.getKey();
-			String locString = Integer.toString(locationId);
-			LandUseItem landUseItem = entry.getValue();
-			
-			double suitableLand = landUseItem.getSuitableArea();
-			totalSuitable += suitableLand;
-			if (DEBUG) LogWriter.println(String.format("  %d   %15s,\t %.3f", locationId, "suitableLand", suitableLand));
-			setGamsParamValueTruncate(landP.addRecord(locString), suitableLand, 5);	
-			
-			for (CropType cropType : CropType.getNonMeatTypes()) {
-				Vector<String> v = new Vector<String>();
-				v.add(cropType.getGamsName());
-				v.add(locString);
-				
-				double area;
-				if (CropType.PASTURE == cropType)
-					area = landUseItem.getLandCoverArea(LandCoverType.PASTURE);
-				else
-					area = landUseItem.getCropArea(cropType);
-				
-				double prevFertI, prevIrrigI, prevOtherI;
-				Intensity intensity = landUseItem.getIntensity(cropType);
-
-				if (intensity==null) { // could be first time through or this crop not previously grown in this location, so give it some default values
-					prevFertI = CropType.PASTURE.equals(cropType) ? 0.0 : 0.5;
-					prevIrrigI = CropType.PASTURE.equals(cropType) ? 0.0 : 0.5;
-					prevOtherI = CropType.PASTURE.equals(cropType) ? 0.1 : 0.5;
-				}
-				else {
-					prevOtherI = intensity.getOtherIntensity();
-					if (prevOtherI == 0 & !CropType.PASTURE.equals(cropType)) {  // this is needed or optimizer gets a bit confused if some areas are kept as cropland but are not productive, due to zero other intensity (i.e. set aside)
-						prevFertI = 0.5;
-						prevIrrigI = 0.5;
-					}
-					else {
-						prevFertI = intensity.getFertiliserIntensity();
-						prevIrrigI = intensity.getIrrigationIntensity();
-					}
-				}
-				
-				if (DEBUG) LogWriter.println(String.format("  %d   %15s,\t %.2f,\t %.3f,\t %.3f,\t %.3f", locationId, cropType.getGamsName(), area, prevFertI, prevIrrigI, prevOtherI));
-				
-				setGamsParamValue(prevCropP.addRecord(v), area, 3);
-				setGamsParamValue(prevFertIP.addRecord(v), prevFertI, 4);
-				setGamsParamValue(prevIrrigIP.addRecord(v), prevIrrigI, 4);
-				setGamsParamValue(prevOtherIP.addRecord(v), prevOtherI, 4);
-				
-				totalAgriLand += area;
-				
-			}
-			
-			// Previous land covers
-			for (LandCoverType lc : LandCoverType.getConvertibleTypes()) {
-				Vector<String> v = new Vector<String>();
-				v.add(lc.getName());
-				v.add(locString);
-				setGamsParamValueTruncate(prevLandCoverP.addRecord(v), landUseItem.getConvertibleLandCoverArea(lc), 6);
-			}
-			
-			// Infrastructure cost TODO
-			double infrastructureCost = landUseItem.getLandCoverFract(LandCoverType.NATURAL) * 1;
-			setGamsParamValue(infrastructureCostP.addRecord(locString), infrastructureCost, 5);
-				
-		}
-		
-		if (DEBUG) LogWriter.println(String.format("  Total agricultural %.1f,\t suitable %.1f", totalAgriLand, totalSuitable));
-
-		if (DEBUG) LogWriter.println("\nIrrigation data (cost, constraint)");
-		GAMSParameter irrigCostP = inDB.addParameter("irrigCost", 1);		
-		GAMSParameter irrigConstraintP = inDB.addParameter("irrigConstraint", 1);
-		Map<Integer, ? extends IrrigationItem> irrigationData = inputData.getIrrigationCosts();
-		
-		double irrigCostMultiplier = ModelConfig.updateParameterForShocks(inputData.getTimestep().getYear(),"IRRIG_COST_MULTIPLIER");
-		
-		for (Entry<Integer, ? extends IrrigationItem> entry : irrigationData.entrySet()) {
-			Integer locationId = entry.getKey();
-			IrrigationItem irrigCostItem = entry.getValue();
-			double irrigCost = irrigCostItem.getIrrigCost()*irrigCostMultiplier;
-			double irrigConstraint = irrigCostItem.getIrrigConstraint();
-			if (DEBUG) LogWriter.println(String.format("  %d  \t %.5f,\t %.1f", locationId, irrigCost, irrigConstraint));
-			setGamsParamValue(irrigCostP.addRecord(Integer.toString(locationId)), irrigCost, 5);
-			setGamsParamValue(irrigConstraintP.addRecord(Integer.toString(locationId)), irrigConstraint, 3);
-		}
-
-		if (DEBUG) LogWriter.println("\nDemand: " + inputData.getCountryInput().getCountry() + " " + inputData.getTimestep().getYear());
-		GamsCountryInput countryInput = inputData.getCountryInput();
-		addCommodityMapParm(inDB.addParameter("demand", 1), countryInput.getProjectedDemand(), -1);
-
-		GAMSParameter minCerealFracP = inDB.addParameter("minDemandPerCereal", 1);
-		GAMSParameter minOilcropsFracP = inDB.addParameter("minDemandPerOilcrop", 1);
-		if (DEBUG) LogWriter.println("\nMinDemandFractions");
-
-		for (Entry<CommodityType, Map<CropType, Double>> entry : countryInput.getMinDemandFractions().entrySet()) {
-			CommodityType comm = entry.getKey();
-			for (Map.Entry<CropType, Double> entry2 : entry.getValue().entrySet()) {
-				CropType crop = entry2.getKey();
-				double minCommFract = ModelConfig.LIMIT_DEMAND_FRACTION ? entry2.getValue() : 0.0;			
-				GAMSParameter minCropsFracP = (comm == CommodityType.CEREALS ? minCerealFracP : minOilcropsFracP);
-				setGamsParamValueTruncate(minCropsFracP.addRecord(crop.getGamsName()), minCommFract, 4);
-				if (DEBUG) LogWriter.println(String.format("  %15s,  %10s,  %.4f", comm.getGamsName(), crop.getGamsName(), minCommFract));
-			}
-		}
-
-		if (DEBUG) LogWriter.println("\nYield (fert/irrig) None/None, Max/None, None/Max, Max/Max, Shock,\t [fert p],\t [irrig p],\t {max irrig}");
-		GAMSParameter yNoneP = inDB.addParameter("yieldNone", 2);
-		GAMSParameter y_fert = inDB.addParameter("yieldFertOnly", 2);
-		GAMSParameter y_irrig = inDB.addParameter("yieldIrrigOnly", 2);
-		GAMSParameter y_both = inDB.addParameter("yieldBoth", 2);
-		GAMSParameter y_shock = inDB.addParameter("yieldShock", 2);
-		GAMSParameter fert_p = inDB.addParameter("fertParam", 2);
-		GAMSParameter irrig_p = inDB.addParameter("irrigParam", 2);
-		GAMSParameter irrigMaxP = inDB.addParameter("irrigMaxRate", 2);
-
-		for (Entry<Integer, ? extends YieldResponsesItem> entry : inputData.getYields().entrySet()) {
-			Integer locationId = entry.getKey();
-			String locString = Integer.toString(locationId);
-			YieldResponsesItem yresp = entry.getValue();
-			IrrigationItem irrigationItem = irrigationData.get(locationId);
-
-			for (CropType crop : CropType.getNonMeatTypes()) {
-				Vector<String> v = new Vector<String>();
-				v.add(crop.getGamsName());
-				v.add(locString);
-				
-				if (crop.equals(CropType.SETASIDE)) {
-					setGamsParamValue(irrigMaxP.addRecord(v), 1000, 3);  // need to set this to any positive value to give an incentive not to irrigate setaside
-					continue;
-				}
-				
-				double maxIrrig = irrigationItem.getMaxIrrigAmount(crop);
-				
-				if (DEBUG) LogWriter.println(String.format("%d      %15s,\t %.1f,\t %.1f, \t %.1f,\t %.1f,\t %.2f,\t\t [%.2f],\t [%.2f],\t {%.2f}", 
-						locationId, crop.getGamsName(), 
-						yresp.getExtrapolatedYield(YieldType.NO_FERT_NO_IRRIG, crop), 
-						yresp.getExtrapolatedYield(YieldType.FERT_MAX_NO_IRRIG, crop), 
-						yresp.getExtrapolatedYield(YieldType.NO_FERT_IRRIG_MAX, crop), 
-						yresp.getExtrapolatedYield(YieldType.FERT_MAX_IRRIG_MAX, crop), 
-						yresp.getShockRate(crop), yresp.getFertParam(crop), yresp.getIrrigParam(crop), maxIrrig));
-
-				setGamsParamValue(yNoneP.addRecord(v), yresp.getExtrapolatedYield(YieldType.NO_FERT_NO_IRRIG, crop), 4);
-				setGamsParamValue(y_fert.addRecord(v), yresp.getExtrapolatedYield(YieldType.FERT_MAX_NO_IRRIG, crop), 4);
-				setGamsParamValue(y_irrig.addRecord(v), yresp.getExtrapolatedYield(YieldType.NO_FERT_IRRIG_MAX, crop), 4);
-				setGamsParamValue(y_both.addRecord(v), yresp.getExtrapolatedYield(YieldType.FERT_MAX_IRRIG_MAX, crop), 4);
-				setGamsParamValue(y_shock.addRecord(v), yresp.getShockRate(crop), 4);
-				setGamsParamValue(fert_p.addRecord(v), yresp.getFertParam(crop), 4);
-				setGamsParamValue(irrig_p.addRecord(v), yresp.getIrrigParam(crop), 4);
-				setGamsParamValue(irrigMaxP.addRecord(v), maxIrrig, 3);
-			}
-		}
-
-		if (DEBUG) LogWriter.println("\nCrop, subsidy rate");
-		GAMSParameter subsideRateP = inDB.addParameter("subsidyRate", 1);
-		for (CropType crop : CropType.getNonMeatTypes()) {
-			double subsidyRate = countryInput.getSubsidyRates().get(crop);
-			if (DEBUG) LogWriter.println(String.format("%15s,\t %.4f", crop.getGamsName(), subsidyRate));
-			setGamsParamValue(subsideRateP.addRecord(crop.getGamsName()), subsidyRate, 4);
-		}
-		
-		if (DEBUG) LogWriter.println("\nImport-export, min trade/prod, max trade/prod, global import price, global export price, imports,   exports, ruminantFeed, monogastricFeed, seedAndWasteRate");
-		
-		GAMSParameter minTradeP = null;
-		GAMSParameter maxTradeP = null;
-		minTradeP = inDB.addParameter("minNetImport", 1);
-		maxTradeP = inDB.addParameter("maxNetImport", 1);
-		
-		GAMSParameter importPriceP = inDB.addParameter("importPrices", 1);
-		GAMSParameter exportPriceP = inDB.addParameter("exportPrices", 1);
-		
-		for (CropType crop : CropType.getImportedTypes()) {		
-			
-			TradeConstraint iec = countryInput.getTradeConstraints().get(crop);
-			CountryPrice gp = countryInput.getCountryPrices().get(crop);			
-			double minTrade = iec.getMinConstraint();
-			double maxTrade = iec.getMaxConstraint();
-			double importPrice = gp.getImportPrice();
-			double exportPrice = gp.getExportPrice();
-			
-			CropUsageData cu = countryInput.getPreviousCropUsageData().get(crop);
-			double netImports = cu.getNetImportsExpected();
-			double imports = netImports>0 ? netImports : 0;
-			double exports = netImports<0 ? -netImports : 0;
-
-			double ruminantFeed = cu.getRuminantFeed();
-			double monogastricFeed = cu.getMonogastricFeed();
-			double seedAndWasteRate = crop.getSeedAndWasteRate();
-					
-			if (DEBUG) LogWriter.println(String.format("     %15s, \t %5.1f, \t %5.1f, \t %5.3f, \t %5.3f,     \t %5.1f, \t %5.1f, \t %5.1f, \t %5.1f, \t %5.3f", 
-					crop.getGamsName(), minTrade, maxTrade, importPrice, exportPrice, imports, exports, ruminantFeed, monogastricFeed, seedAndWasteRate));
-			
-			setGamsParamValue(minTradeP.addRecord(crop.getGamsName()), minTrade, 3);
-			setGamsParamValue(maxTradeP.addRecord(crop.getGamsName()), maxTrade, 3);
-			setGamsParamValue(importPriceP.addRecord(crop.getGamsName()), importPrice, 3);
-			setGamsParamValue(exportPriceP.addRecord(crop.getGamsName()), exportPrice, 3);
-			setGamsParamValue(previousImportAmountP.addRecord(crop.getGamsName()), imports, 3);
-			setGamsParamValue(previousExportAmountP.addRecord(crop.getGamsName()), exports, 3);
-			setGamsParamValue(previousRuminantFeedP.addRecord(crop.getGamsName()), ruminantFeed, 3);
-			setGamsParamValue(previousMonogastricFeedP.addRecord(crop.getGamsName()), monogastricFeed, 3);
-			setGamsParamValue(seedAndWasteRateP.addRecord(crop.getGamsName()), seedAndWasteRate, 3);
-		}
-		
-
-		//below in case running without shocks to use original values in model config 
-		
-		double meatEff = ModelConfig.updateParameterForShocks(inputData.getTimestep().getYear(), "MEAT_EFFICIENCY");
-		double fertCost = ModelConfig.updateParameterForShocks(inputData.getTimestep().getYear(), "FERTILISER_COST_PER_T");
-		double otherIntCost = ModelConfig.updateParameterForShocks(inputData.getTimestep().getYear(), "OTHER_INTENSITY_COST");
-
-		LogWriter.print("\n");	
-		addScalar(inDB, "meatEfficency", meatEff, 3);		
-		addScalar(inDB, "fertiliserUnitCost", fertCost, 3);
-		addScalar(inDB, "otherICost",otherIntCost, 3);
-		addScalar(inDB, "otherIParam", ModelConfig.OTHER_INTENSITY_PARAM, 3);
-		addScalar(inDB, "unhandledCropRate", ModelConfig.UNHANDLED_CROP_RATE, 3);
-		addScalar(inDB, "setAsideRate", ModelConfig.SETASIDE_RATE, 5);
-		addScalar(inDB, "domesticPriceMarkup", ModelConfig.DOMESTIC_PRICE_MARKUP, 3);
-		double maxExpansion = 1.0;
-		if (!ModelConfig.IS_CALIBRATION_RUN && countryInput.getCountry().getName().equals("China")) {
-			maxExpansion = ModelConfig.MAX_CHINA_LAND_EXPANSION_RATE;
-		}
-		addScalar(inDB, "maxLandExpansionRate", maxExpansion, 3);
-		
-		addScalar(inDB, "carbonPrice", countryInput.getCarbonPrice().getExportPrice(), 5);
-		addScalar(inDB, "woodExportPrice", countryInput.getWoodPrice().getExportPrice(), 5);
-		addScalar(inDB, "woodImportPrice", countryInput.getWoodPrice().getImportPrice(), 5);
-		
-		// Not simulating production of different wood products so need to aggregate
-		double woodMinNetImport = 0;
-		double woodMaxNetImport = 0;
-		Map<WoodType, TradeConstraint> woodTradeConstraints = countryInput.getWoodTradeConstraints();
-		for (TradeConstraint tc: woodTradeConstraints.values()) {
-			woodMinNetImport += tc.getMinConstraint();
-			woodMaxNetImport += tc.getMaxConstraint();
-		}
-		
-		addScalar(inDB, "woodMaxNetImport", woodMaxNetImport, 5);
-		addScalar(inDB, "woodMinNetImport", woodMinNetImport, 5);
-		
-		// Wood demand
-		Map<WoodType, Double> woodDemandMap = countryInput.getWoodDemand();
-		double totalWoodDemand = woodDemandMap.values().stream().reduce(0.0, Double::sum);
-		addScalar(inDB, "woodDemand", totalWoodDemand, 5);
-		
-		
-		// Carbon fluxes
-		GAMSParameter cFluxRateP = inDB.addParameter("carbonFluxRate", 3);
-		GAMSParameter cFluxRateNeeP = inDB.addParameter("carbonFluxRateNEE", 2);
-		
-		for (Entry<Integer, ? extends CarbonFluxItem> entry : inputData.getCarbonFluxes().entrySet()) {
-			Integer locationId = entry.getKey();
-			String locString = Integer.toString(locationId);
-			CarbonFluxItem cFlux = entry.getValue();
-			
-			for (Map.Entry<LccKey, Double> cfEntry : cFlux.getConversionCarbonFluxMap().entrySet()) {
-				LccKey key = cfEntry.getKey();
-				Vector<String> v = new Vector<String>();
-				v.add(key.getFromLc().getName());
-				v.add(key.getToLc().getName());
-				v.add(locString);
-				setGamsParamValue(cFluxRateP.addRecord(v), cfEntry.getValue(), 2);
-			}
-			
-			for (Map.Entry<LandCoverType, Double> cfEntry : cFlux.getEcosystemCarbonFluxMap().entrySet()) {
-				Vector<String> v = new Vector<String>();
-				v.add(cfEntry.getKey().getName());
-				v.add(locString);
-				setGamsParamValue(cFluxRateNeeP.addRecord(v), cfEntry.getValue(), 3);
-			}	
-		}
-		
-		// Yield from timber forest rotation
-		Map<Integer, ? extends WoodYieldData> woodYieldData = inputData.getWoodYields();
-		
-		GAMSParameter woodYieldRotaP = inDB.addParameter("woodYieldRota", 1);
-		GAMSParameter woodYieldLuc = inDB.addParameter("woodYieldLUC", 3);
-		
-		for (Entry<Integer, ? extends WoodYieldData> entry : woodYieldData.entrySet()) {
-			Integer locationId = entry.getKey();
-			String locString = Integer.toString(locationId);
-			WoodYieldData wYield = entry.getValue();
-
-			Vector<String> v = new Vector<String>();
-			v.add(locString);
-			setGamsParamValue(woodYieldRotaP.addRecord(v), wYield.getYieldRota(), 5);
-			
-			for (LandCoverType toLc : LandCoverType.getAgriculturalTypes()) {
-				Vector<String> w = new Vector<String>();
-				w.add(LandCoverType.NATURAL.getName());
-				w.add(toLc.getName());
-				w.add(locString);
-				setGamsParamValue(woodYieldLuc.addRecord(w), wYield.getYieldLuc(LandCoverType.NATURAL, toLc), 5);
-			}
-
-		}	
-		
-		// Land cover conversion cost
-		GAMSParameter conversionCostP = inDB.addParameter("conversionCost", 2);
-		Map<LccKey, Double> conversionCosts = inputData.getConversionCosts();
-		
-		for (Map.Entry<LccKey, Double> entry : conversionCosts.entrySet()) {
-			LccKey key = entry.getKey();
-			String fromName = key.getFromLc().getName();
-			String toName = key.getToLc().getName();
-			double cost = entry.getValue();
-			Vector<String> v = new Vector<String>();
-			v.add(fromName);
-			v.add(toName);
-			setGamsParamValue(conversionCostP.addRecord(v), cost, 5);
-		}
-		
-		GAMSParameter forestRotationP = inDB.addParameter("forestRotationPeriod", 1);
-		for (Map.Entry<Integer, Double> entry : inputData.getRotationPeriods().entrySet()) {
-			int locationId = entry.getKey();
-			double rotPeriod = entry.getValue();
-			String locString = Integer.toString(locationId);
-			setGamsParamValue(forestRotationP.addRecord(locString), rotPeriod, 0);
-		}
-		
-		addScalar(inDB, "forestEstablishmentCost", ModelConfig.FOREST_ESTABLISHMENT_COST, 5);
-		double managedWoodHarvest = countryInput.getWoodHarvestFromRotation();
-		addScalar(inDB, "woodFromRotation", managedWoodHarvest, 5);
-		addScalar(inDB, "vegClearingCostRate", ModelConfig.VEGETATION_CLEARING_COST, 5);
-	}
-
-	private void addScalar(GAMSDatabase gamsDb, String recordName, double val, int places) {
-		GAMSParameter param = gamsDb.addParameter(recordName, 0);
-		double dOut = setGamsParamValue(param.addRecord(), val, places);
-		if (DEBUG) LogWriter.println(recordName + ": " + dOut);
-	}
-
-	private double setGamsParamValue(GAMSParameterRecord param, double val, int places) {
-		double dOut = places >= 0 ? Math.round(val * Math.pow(10,places)) / Math.pow(10,places) : val;
-		param.setValue(dOut);
-		return dOut;
-	}
-	
-	private double setGamsParamValueTruncate(GAMSParameterRecord param, double val, int places) {
-		double dOut = ((int)(val * Math.pow(10,places))) / Math.pow(10,places);
-		param.setValue(dOut);
-		return dOut;
-	}
-	
-	@SuppressWarnings("serial")
-	private GamsLocationOutput handleResults(GAMSDatabase outDB) {
-		int modelStatusInt = (int) outDB.getParameter("ms").findRecord().getValue();
-		ModelStat modelStatus = GAMSGlobals.ModelStat.lookup(modelStatusInt);
-		String contextString = String.format("%s %s: Modelstatus %s, Solvestatus %s", 
-    			inputData.getCountryInput().getCountry(),
-			inputData.getTimestep().getYear(),
-			modelStatus.toString(),
-			GAMSGlobals.SolveStat.lookup((int) outDB.getParameter("ss").findRecord().getValue()));
-        LogWriter.println("\n" + contextString);
-
-		if (modelStatus != ModelStat.OPTIMAL_LOCAL) {
-			LogWriter.printlnError("Critical!!! Land use incorrectly solved. " + contextString);
-		}
-		
-		GAMSVariable varAreas = outDB.getVariable("cropArea");
-		GAMSVariable varFertIntensities = outDB.getVariable("fertI");
-		GAMSVariable varIrrigIntensities = outDB.getVariable("irrigI");
-		GAMSVariable varOtherIntensities = outDB.getVariable("otherIntensity");
-		GAMSVariable varRuminantFeed = outDB.getVariable("ruminantFeed");
-		GAMSVariable varMonogastricFeed = outDB.getVariable("monogastricFeed");
-		GAMSParameter parmNetImports = outDB.getParameter("netImportAmount");
-		GAMSParameter parmNetImportCost = outDB.getParameter("netImportCost");
-		GAMSVariable varYields = outDB.getVariable("yield");
-		GAMSVariable varUnitEnergies = outDB.getVariable("unitCost");
-		GAMSParameter parmProd = outDB.getParameter("totalProd");
-		GAMSParameter parmProdCost = outDB.getParameter("totalProdCost");
-		GAMSParameter parmCroplandArea = outDB.getParameter("totalCropland");
-		GAMSParameter parmTotalArea = outDB.getParameter("totalArea");
-		GAMSParameter parmProdShock = outDB.getParameter("productionShock");		
-		
-		double totalCropArea = 0;
-		double totalPastureArea = 0;
-		double area, fertIntensity, irrigIntensity, otherIntensity = Double.NaN, ruminantFeed, monogastricFeed, netImport, netImportCost, yield, unitCost, prod, prodCost;	
-		
-		final LazyHashMap<Integer, LandUseItem> landUses = new LazyHashMap<Integer, LandUseItem>() { 
-			protected LandUseItem createValue() { return new LandUseItem(); }
-		};		
-		
-		Map<Integer, ? extends IrrigationItem> allIrrigationRefData = inputData.getIrrigationCosts();
-
-		Map<CropType, CropUsageData> cropUsageData = new HashMap<CropType, CropUsageData>();
-
-		for (GAMSVariableRecord rec : varAreas) {
-			String itemName = rec.getKeys()[0];
-			String locationName = rec.getKeys()[1];
-			area = rec.getLevel();
-			fertIntensity = varFertIntensities.findRecord(itemName, locationName).getLevel();
-			irrigIntensity = varIrrigIntensities.findRecord(itemName, locationName).getLevel();
-			otherIntensity = varOtherIntensities.findRecord(itemName, locationName).getLevel();
-			yield = varYields.findRecord(itemName, locationName).getLevel();
-			unitCost = varUnitEnergies.findRecord(itemName, locationName).getLevel();
-
-			int locId = Integer.parseInt(locationName);
-			CropType cropType = CropType.getForGamsName(itemName);
-
-			if (!cropUsageData.containsKey(cropType)) {  // then we must not have seen this crop type before, so need to do all non location specific stuff
-				ruminantFeed = varRuminantFeed.findRecord(itemName).getLevel();
-				monogastricFeed = varMonogastricFeed.findRecord(itemName).getLevel();
-				netImport = cropType.isImportedCrop() ? getParmValue(parmNetImports, itemName) : 0;
-				netImportCost = cropType.isImportedCrop() ? getParmValue(parmNetImportCost, itemName) : 0;
-				prod =  getParmValue(parmProd, itemName);
-				prodCost = getParmValue(parmProdCost, itemName);
-				double totalArea = getParmValue(parmTotalArea, itemName);
-				double prodShock = getParmValue(parmProdShock, itemName);
-
-				cropUsageData.put(cropType, new CropUsageData(ruminantFeed, monogastricFeed, netImport, netImportCost, prod, prodCost, totalArea, prodShock));
-				if (DEBUG) LogWriter.println(String.format("\n%s:\tarea= %.1f,\tmonogastricFeed= %.1f,\truminantFeed= %.1f,\tnetImports= %.3f,\tnetImportCost= %.3f,\tprod= %.3f, \tprodCost= %.3f,\tprodCostRate= %.3f,\tprodShock= %.3f", itemName, totalArea, monogastricFeed, ruminantFeed, netImport, netImportCost, prod, prodCost, prodCost/prod, prodShock)); 
-			
-			}
-
-			LandUseItem landUseItem = landUses.lazyGet(locId); // returns landUseItem for location. If does not exist, creates new one
-
-			if (area > 0) { 
-
-				if (DEBUG) LogWriter.println(String.format("\t location %s, %s:\tarea= %.1f,\tfert= %.3f,\tirrg= %.3f,\tintensity= %.3f", locationName, itemName, area, fertIntensity, irrigIntensity, otherIntensity));				
-				IrrigationItem irrigRefData = allIrrigationRefData.get(locId);
-				landUseItem.setIntensity(cropType, new Intensity(fertIntensity, irrigIntensity, otherIntensity, yield, unitCost, irrigRefData.getMaxIrrigAmount(cropType)));
-			}
-			
-			
-			double croplandArea = getParmValue(parmCroplandArea, locationName);
-
-			landUseItem.setCropFraction(cropType, croplandArea > 0 ? area/croplandArea : 0); // TODO gives wrong pasture numbers
-			
-		}
-
-		for (CropType meatTypes : CropType.getMeatTypes()) {
-			netImport = getParmValue(parmNetImports, meatTypes.getGamsName());
-			netImportCost= getParmValue(parmNetImportCost, meatTypes.getGamsName());
-			prod = getParmValue(parmProd, meatTypes.getGamsName());
-			prodCost = getParmValue(parmProdCost, meatTypes.getGamsName());
-
-			cropUsageData.put(meatTypes, new CropUsageData(0.0, 0.0, netImport, netImportCost, prod, prodCost, Double.NaN, 0));
-			if (DEBUG) LogWriter.println(String.format("\n%s:\t\t\t\t\tnetImports= %.3f,\tnetImportCost= %.3f,\tprod= %.3f,\tprodCost= %.3f", meatTypes.getGamsName(), netImport, netImportCost, prod, prodCost)); 
-		}
-		LogWriter.println(String.format("\n%s %s: Total area= %.1f (crop=%.1f, pasture %.1f)", 
-				inputData.getCountryInput().getCountry(), inputData.getTimestep().getYear(), 
-				totalCropArea+totalPastureArea, totalCropArea, totalPastureArea));
-			
-		// Land cover change
-		GAMSVariable varLandCoverChange = outDB.getVariable("landCoverChange");
-		Map<Integer, ArrayList<LandCoverChangeItem>> landCoverChanges = new HashMap<Integer, ArrayList<LandCoverChangeItem>>();
-		
-		for (GAMSVariableRecord rec : varLandCoverChange) {
-			String fromLcStr = rec.getKeys()[0];
-			String toLcStr = rec.getKeys()[1];
-			String locationName = rec.getKeys()[2];
-			int locId = Integer.parseInt(locationName);
-			double change = rec.getLevel();
-			
-			// Skip if no change or no conversion
-			if (change == 0 || fromLcStr.equals(toLcStr)) 
-				continue;
-
-			LandCoverType fromLc = LandCoverType.getForName(fromLcStr);
-			LandCoverType toLc = LandCoverType.getForName(toLcStr);	
-			
-			ArrayList<LandCoverChangeItem> changesList = landCoverChanges.computeIfAbsent(locId, k -> new ArrayList<LandCoverChangeItem>());
-			changesList.add(new LandCoverChangeItem(fromLc, toLc, change));
-		}
-
-		// Carbon flux
-		double netCarbonFlux = outDB.getParameter("netCarbonFlux").getFirstRecord().getValue();
-		
-		// Timber harvest
-		double totalWoodHarvest = outDB.getParameter("totalWoodHarvest").getFirstRecord().getValue();
-		double netWoodImport = outDB.getParameter("netWoodImport").getFirstRecord().getValue();
-		
-		// Need to disaggregate wood harvest and import by type. This is just for future proofing. Eventually, could have separate harvest for each wood type.
-		Map<WoodType, WoodUsageData> newWoodUsageMap = new HashMap<WoodType, WoodUsageData>();
-		Map<WoodType, WoodUsageData> previousWoodUsageMap = inputData.getCountryInput().getPreviousWoodUsageData();
-		double previousNetImportSum = previousWoodUsageMap.values().stream().mapToDouble(o -> o.getNetImport()).sum();
-		double netWoodImportChange = netWoodImport - previousNetImportSum;
-		double previousWoodHarvestSum = previousWoodUsageMap.values().stream().mapToDouble(o -> o.getHarvest()).sum();
-		int numWoodTypes = WoodType.values().length;
-		for (WoodType wt : WoodType.values()) {
-			double previousNetImport = previousWoodUsageMap.get(wt).getNetImport();
-			double newNetImport = previousNetImport + netWoodImportChange / numWoodTypes;
-			double previousHarvest = previousWoodUsageMap.get(wt).getHarvest();
-			// assuming equal split if no previous harvest
-			double newHarvest = (previousWoodHarvestSum > 0) ? totalWoodHarvest * (previousHarvest / previousWoodHarvestSum) : totalWoodHarvest / numWoodTypes;
-			newWoodUsageMap.put(wt, new WoodUsageData(newHarvest, newNetImport));
-		}
-		
-		Map<Integer, Double> naturalForestCleared = new HashMap<Integer, Double>();
-
-		GamsLocationOutput results = new GamsLocationOutput(modelStatus, landUses, cropUsageData, landCoverChanges, netCarbonFlux, newWoodUsageMap, naturalForestCleared);
-		return results;
-	}
-	
-	/*
-	private double getPrevUnmanagedForestProp(Integer locId) {
-		double prevOtherNaturalArea = inputData.getPreviousLandUse().get(locId).getUnprotectedLandCoverArea(LandCoverType.OTHER_NATURAL);
-		double prevUnmanagedForestArea = inputData.getPreviousLandUse().get(locId).getUnprotectedLandCoverArea(LandCoverType.UNMANAGED_FOREST);
-		double prevUnmanagedForestProp = (prevOtherNaturalArea + prevUnmanagedForestArea) != 0 ? prevUnmanagedForestArea / (prevUnmanagedForestArea + prevOtherNaturalArea) : 0;
-		return prevUnmanagedForestProp;
-	}
-	*/
-
-	private double getParmValue(GAMSParameter aParm, String itemName) {
-		try {
-			GAMSParameterRecord record = aParm.findRecord(itemName);
-			double d = record.getValue();
-			return d;
-		}
-		catch (GAMSException gamsEx) {
-			//LogWriter.println("GAMSException thrown for " + itemName);
-			return 0;
-		}
-	}
-
-	private void addCommodityMapParm(GAMSParameter parm, Map<CommodityType, Double> itemMap, int places) {
-		for (Map.Entry<CommodityType, Double> entry : itemMap.entrySet()) {
-			double d = entry.getValue();
-			if (DEBUG) LogWriter.println(String.format("     %15s,\t %.1f", entry.getKey().getGamsName(), d));
-			if (!Double.isNaN(d))
-				setGamsParamValue(parm.addRecord(entry.getKey().getGamsName()), d, places);
-		}
-	}
-
-
-	private void cleanup(String directory)  {
-		File directoryToDelete = new File(directory);
-		String files[] = directoryToDelete.list();
-		for (String file : files) {
-			File fileToDelete = new File(directoryToDelete, file);
-			try {
-				fileToDelete.delete();
-			} catch(Exception e){
-				LogWriter.print(e);
-			}
-		}
-		try {
-			directoryToDelete.delete();
-		} catch(Exception e) {
-			LogWriter.print(e);
-		}
-	}
-}
+package ac.ed.lurg.country.gams;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Vector;
+
+import com.gams.api.GAMSDatabase;
+import com.gams.api.GAMSException;
+import com.gams.api.GAMSGlobals;
+import com.gams.api.GAMSGlobals.ModelStat;
+import com.gams.api.GAMSJob;
+import com.gams.api.GAMSOptions;
+import com.gams.api.GAMSParameter;
+import com.gams.api.GAMSParameterRecord;
+import com.gams.api.GAMSSet;
+import com.gams.api.GAMSVariable;
+import com.gams.api.GAMSVariableRecord;
+import com.gams.api.GAMSWorkspace;
+import com.gams.api.GAMSWorkspaceInfo;
+
+import ac.ed.lurg.ModelConfig;
+import ac.ed.lurg.carbon.CarbonFluxItem;
+import ac.ed.lurg.country.CountryPrice;
+import ac.ed.lurg.country.LandCoverChangeItem;
+import ac.ed.lurg.country.TradeConstraint;
+import ac.ed.lurg.forestry.WoodYieldData;
+import ac.ed.lurg.landuse.CropUsageData;
+import ac.ed.lurg.landuse.Intensity;
+import ac.ed.lurg.landuse.IrrigationItem;
+import ac.ed.lurg.landuse.LandUseItem;
+import ac.ed.lurg.landuse.LccKey;
+import ac.ed.lurg.landuse.WoodUsageData;
+import ac.ed.lurg.types.CommodityType;
+import ac.ed.lurg.types.CropType;
+import ac.ed.lurg.types.LandCoverType;
+import ac.ed.lurg.types.LandProtectionType;
+import ac.ed.lurg.types.WoodType;
+import ac.ed.lurg.types.YieldType;
+import ac.ed.lurg.utils.LazyHashMap;
+import ac.ed.lurg.utils.LogWriter;
+import ac.ed.lurg.yield.YieldResponsesItem;
+
+public class GamsLocationOptimiser {
+
+	private static final boolean DEBUG = false;
+
+	private GamsLocationInput inputData;
+
+	public GamsLocationOptimiser(GamsLocationInput inputData) {
+		this.inputData = inputData;
+	}
+
+	public GamsLocationOutput run() {
+
+		File workingDirectory = new File(ModelConfig.TEMP_DIR);
+		workingDirectory.mkdir();
+
+		GAMSWorkspaceInfo  wsInfo  = new GAMSWorkspaceInfo();
+		wsInfo.setWorkingDirectory(workingDirectory.getAbsolutePath());
+		//	wsInfo.setDebugLevel(DebugLevel.VERBOSE);
+
+		GAMSWorkspace ws = new GAMSWorkspace(wsInfo);
+		GAMSDatabase inDB = ws.addDatabase();
+
+		setupInDB(inDB);
+
+		GAMSJob gamsJob = ws.addJobFromFile(ModelConfig.GAMS_MODEL);
+		GAMSOptions opt = ws.addOptions();
+
+		opt.setAllModelTypes("conopt");
+
+		opt.defines("gdxincname", inDB.getName());
+
+		long startTime = System.currentTimeMillis();
+		gamsJob.run(opt, inDB);	
+		
+		if (ModelConfig.CLEANUP_GAMS_DIR) 
+			cleanup(ws.workingDirectory());
+		
+		LogWriter.println("Took " + (System.currentTimeMillis() - startTime) + " ms to run");
+		
+
+		return handleResults(gamsJob.OutDB());
+		
+	}
+
+	private void setupInDB(GAMSDatabase inDB) {
+		//if (DEBUG) LogWriter.println("\nLocation set");
+		GAMSSet locationSet = inDB.addSet("location", 1);
+		for (Integer locId : inputData.getPreviousLandUse().keySet()) {
+			//if (DEBUG) LogWriter.println("     " + locId);
+			locationSet.addRecord(locId.toString());
+		}
+
+		if (DEBUG) LogWriter.println("\nPrevious crop and land areas");	
+		GAMSParameter prevCropP = inDB.addParameter("previousCropArea", 2);
+		GAMSParameter prevFertIP = inDB.addParameter("previousFertIntensity", 2);
+		GAMSParameter prevIrrigIP = inDB.addParameter("previousIrrigIntensity", 2);
+		GAMSParameter prevOtherIP = inDB.addParameter("previousOtherIntensity", 2);
+		
+		GAMSParameter previousRuminantFeedP = inDB.addParameter("previousRuminantFeed", 1);
+		GAMSParameter previousMonogastricFeedP = inDB.addParameter("previousMonogastricFeed", 1);
+		GAMSParameter previousImportAmountP = inDB.addParameter("previousImportAmount", 1);
+		GAMSParameter previousExportAmountP = inDB.addParameter("previousExportAmount", 1);
+	
+		GAMSParameter landP = inDB.addParameter("suitableLandArea", 1);
+		GAMSParameter seedAndWasteRateP = inDB.addParameter("seedAndWasteRate", 1);	
+		
+		GAMSParameter prevLandCoverP = inDB.addParameter("previousLandCoverArea", 2);
+		
+		double totalAgriLand = 0;
+		double totalSuitable = 0;
+
+		for (Map.Entry<Integer, ? extends LandUseItem> entry : inputData.getPreviousLandUse().entrySet()) {
+			Integer locationId = entry.getKey();
+			String locString = Integer.toString(locationId);
+			LandUseItem landUseItem = entry.getValue();
+			
+			double suitableLand = landUseItem.getSuitableArea();
+			totalSuitable += suitableLand;
+			if (DEBUG) LogWriter.println(String.format("  %d   %15s,\t %.3f", locationId, "suitableLand", suitableLand));
+			setGamsParamValueTruncate(landP.addRecord(locString), suitableLand, 5);	
+			
+			for (CropType cropType : CropType.getNonMeatTypes()) {
+				Vector<String> v = new Vector<String>();
+				v.add(cropType.getGamsName());
+				v.add(locString);
+				
+				double area;
+				if (CropType.PASTURE == cropType)
+					area = landUseItem.getLandCoverArea(LandCoverType.PASTURE);
+				else
+					area = landUseItem.getCropArea(cropType);
+				
+				double prevFertI, prevIrrigI, prevOtherI;
+				Intensity intensity = landUseItem.getIntensity(cropType);
+
+				if (intensity==null) { // could be first time through or this crop not previously grown in this location, so give it some default values
+					prevFertI = CropType.PASTURE.equals(cropType) ? 0.0 : 0.5;
+					prevIrrigI = CropType.PASTURE.equals(cropType) ? 0.0 : 0.5;
+					prevOtherI = CropType.PASTURE.equals(cropType) ? 0.1 : 0.5;
+				}
+				else {
+					prevOtherI = intensity.getOtherIntensity();
+					if (prevOtherI == 0 & !CropType.PASTURE.equals(cropType)) {  // this is needed or optimizer gets a bit confused if some areas are kept as cropland but are not productive, due to zero other intensity (i.e. set aside)
+						prevFertI = 0.5;
+						prevIrrigI = 0.5;
+					}
+					else {
+						prevFertI = intensity.getFertiliserIntensity();
+						prevIrrigI = intensity.getIrrigationIntensity();
+					}
+				}
+				
+				if (DEBUG) LogWriter.println(String.format("  %d   %15s,\t %.2f,\t %.3f,\t %.3f,\t %.3f", locationId, cropType.getGamsName(), area, prevFertI, prevIrrigI, prevOtherI));
+				
+				setGamsParamValue(prevCropP.addRecord(v), area, 3);
+				setGamsParamValue(prevFertIP.addRecord(v), prevFertI, 4);
+				setGamsParamValue(prevIrrigIP.addRecord(v), prevIrrigI, 4);
+				setGamsParamValue(prevOtherIP.addRecord(v), prevOtherI, 4);
+				
+				totalAgriLand += area;
+				
+			}
+			
+			// Previous land covers
+			for (LandCoverType lc : LandCoverType.getConvertibleTypes()) {
+				Vector<String> v = new Vector<String>();
+				v.add(lc.getName());
+				v.add(locString);
+				double area = landUseItem.getLandCoverArea(lc, LandProtectionType.CONVERTIBLE);
+				setGamsParamValueTruncate(prevLandCoverP.addRecord(v), area, 6);
+			}				
+		}
+		
+		if (DEBUG) LogWriter.println(String.format("  Total agricultural %.1f,\t suitable %.1f", totalAgriLand, totalSuitable));
+
+		if (DEBUG) LogWriter.println("\nIrrigation data (cost, constraint)");
+		GAMSParameter irrigCostP = inDB.addParameter("irrigCost", 1);		
+		GAMSParameter irrigConstraintP = inDB.addParameter("irrigConstraint", 1);
+		Map<Integer, ? extends IrrigationItem> irrigationData = inputData.getIrrigationCosts();
+		
+		double irrigCostMultiplier = ModelConfig.updateParameterForShocks(inputData.getTimestep().getYear(),"IRRIG_COST_MULTIPLIER");
+		
+		for (Entry<Integer, ? extends IrrigationItem> entry : irrigationData.entrySet()) {
+			Integer locationId = entry.getKey();
+			IrrigationItem irrigCostItem = entry.getValue();
+			double irrigCost = irrigCostItem.getIrrigCost()*irrigCostMultiplier;
+			double irrigConstraint = irrigCostItem.getIrrigConstraint();
+			if (DEBUG) LogWriter.println(String.format("  %d  \t %.5f,\t %.1f", locationId, irrigCost, irrigConstraint));
+			setGamsParamValue(irrigCostP.addRecord(Integer.toString(locationId)), irrigCost, 5);
+			setGamsParamValue(irrigConstraintP.addRecord(Integer.toString(locationId)), irrigConstraint, 3);
+		}
+
+		if (DEBUG) LogWriter.println("\nDemand: " + inputData.getCountryInput().getCountry() + " " + inputData.getTimestep().getYear());
+		GamsCountryInput countryInput = inputData.getCountryInput();
+		addCommodityMapParm(inDB.addParameter("demand", 1), countryInput.getProjectedDemand(), -1);
+
+		GAMSParameter minCerealFracP = inDB.addParameter("minDemandPerCereal", 1);
+		GAMSParameter minOilcropsFracP = inDB.addParameter("minDemandPerOilcrop", 1);
+		if (DEBUG) LogWriter.println("\nMinDemandFractions");
+
+		for (Entry<CommodityType, Map<CropType, Double>> entry : countryInput.getMinDemandFractions().entrySet()) {
+			CommodityType comm = entry.getKey();
+			for (Map.Entry<CropType, Double> entry2 : entry.getValue().entrySet()) {
+				CropType crop = entry2.getKey();
+				double minCommFract = ModelConfig.LIMIT_DEMAND_FRACTION ? entry2.getValue() : 0.0;			
+				GAMSParameter minCropsFracP = (comm == CommodityType.CEREALS ? minCerealFracP : minOilcropsFracP);
+				setGamsParamValueTruncate(minCropsFracP.addRecord(crop.getGamsName()), minCommFract, 4);
+				if (DEBUG) LogWriter.println(String.format("  %15s,  %10s,  %.4f", comm.getGamsName(), crop.getGamsName(), minCommFract));
+			}
+		}
+
+		if (DEBUG) LogWriter.println("\nYield (fert/irrig) None/None, Max/None, None/Max, Max/Max, Shock,\t [fert p],\t [irrig p],\t {max irrig}");
+		GAMSParameter yNoneP = inDB.addParameter("yieldNone", 2);
+		GAMSParameter y_fert = inDB.addParameter("yieldFertOnly", 2);
+		GAMSParameter y_irrig = inDB.addParameter("yieldIrrigOnly", 2);
+		GAMSParameter y_both = inDB.addParameter("yieldBoth", 2);
+		GAMSParameter y_shock = inDB.addParameter("yieldShock", 2);
+		GAMSParameter fert_p = inDB.addParameter("fertParam", 2);
+		GAMSParameter irrig_p = inDB.addParameter("irrigParam", 2);
+		GAMSParameter irrigMaxP = inDB.addParameter("irrigMaxRate", 2);
+
+		for (Entry<Integer, ? extends YieldResponsesItem> entry : inputData.getYields().entrySet()) {
+			Integer locationId = entry.getKey();
+			String locString = Integer.toString(locationId);
+			YieldResponsesItem yresp = entry.getValue();
+			IrrigationItem irrigationItem = irrigationData.get(locationId);
+
+			for (CropType crop : CropType.getNonMeatTypes()) {
+				Vector<String> v = new Vector<String>();
+				v.add(crop.getGamsName());
+				v.add(locString);
+				
+				if (crop.equals(CropType.SETASIDE)) {
+					setGamsParamValue(irrigMaxP.addRecord(v), 1000, 3);  // need to set this to any positive value to give an incentive not to irrigate setaside
+					continue;
+				}
+				
+				double maxIrrig = irrigationItem.getMaxIrrigAmount(crop);
+				
+				if (DEBUG) LogWriter.println(String.format("%d      %15s,\t %.1f,\t %.1f, \t %.1f,\t %.1f,\t %.2f,\t\t [%.2f],\t [%.2f],\t {%.2f}", 
+						locationId, crop.getGamsName(), 
+						yresp.getExtrapolatedYield(YieldType.NO_FERT_NO_IRRIG, crop), 
+						yresp.getExtrapolatedYield(YieldType.FERT_MAX_NO_IRRIG, crop), 
+						yresp.getExtrapolatedYield(YieldType.NO_FERT_IRRIG_MAX, crop), 
+						yresp.getExtrapolatedYield(YieldType.FERT_MAX_IRRIG_MAX, crop), 
+						yresp.getShockRate(crop), yresp.getFertParam(crop), yresp.getIrrigParam(crop), maxIrrig));
+
+				setGamsParamValue(yNoneP.addRecord(v), yresp.getExtrapolatedYield(YieldType.NO_FERT_NO_IRRIG, crop), 4);
+				setGamsParamValue(y_fert.addRecord(v), yresp.getExtrapolatedYield(YieldType.FERT_MAX_NO_IRRIG, crop), 4);
+				setGamsParamValue(y_irrig.addRecord(v), yresp.getExtrapolatedYield(YieldType.NO_FERT_IRRIG_MAX, crop), 4);
+				setGamsParamValue(y_both.addRecord(v), yresp.getExtrapolatedYield(YieldType.FERT_MAX_IRRIG_MAX, crop), 4);
+				setGamsParamValue(y_shock.addRecord(v), yresp.getShockRate(crop), 4);
+				setGamsParamValue(fert_p.addRecord(v), yresp.getFertParam(crop), 4);
+				setGamsParamValue(irrig_p.addRecord(v), yresp.getIrrigParam(crop), 4);
+				setGamsParamValue(irrigMaxP.addRecord(v), maxIrrig, 3);
+			}
+		}
+
+		if (DEBUG) LogWriter.println("\nCrop, subsidy rate");
+		GAMSParameter subsideRateP = inDB.addParameter("subsidyRate", 1);
+		for (CropType crop : CropType.getNonMeatTypes()) {
+			double subsidyRate = countryInput.getSubsidyRates().get(crop);
+			if (DEBUG) LogWriter.println(String.format("%15s,\t %.4f", crop.getGamsName(), subsidyRate));
+			setGamsParamValue(subsideRateP.addRecord(crop.getGamsName()), subsidyRate, 4);
+		}
+		
+		if (DEBUG) LogWriter.println("\nImport-export, min trade/prod, max trade/prod, global import price, global export price, imports,   exports, ruminantFeed, monogastricFeed, seedAndWasteRate");
+		
+		GAMSParameter minTradeP = null;
+		GAMSParameter maxTradeP = null;
+		minTradeP = inDB.addParameter("minNetImport", 1);
+		maxTradeP = inDB.addParameter("maxNetImport", 1);
+		
+		GAMSParameter importPriceP = inDB.addParameter("importPrices", 1);
+		GAMSParameter exportPriceP = inDB.addParameter("exportPrices", 1);
+		
+		for (CropType crop : CropType.getImportedTypes()) {		
+			
+			TradeConstraint iec = countryInput.getTradeConstraints().get(crop);
+			CountryPrice gp = countryInput.getCountryPrices().get(crop);			
+			double minTrade = iec.getMinConstraint();
+			double maxTrade = iec.getMaxConstraint();
+			double importPrice = gp.getImportPrice();
+			double exportPrice = gp.getExportPrice();
+			
+			CropUsageData cu = countryInput.getPreviousCropUsageData().get(crop);
+			double netImports = cu.getNetImportsExpected();
+			double imports = netImports>0 ? netImports : 0;
+			double exports = netImports<0 ? -netImports : 0;
+
+			double ruminantFeed = cu.getRuminantFeed();
+			double monogastricFeed = cu.getMonogastricFeed();
+			double seedAndWasteRate = crop.getSeedAndWasteRate();
+					
+			if (DEBUG) LogWriter.println(String.format("     %15s, \t %5.1f, \t %5.1f, \t %5.3f, \t %5.3f,     \t %5.1f, \t %5.1f, \t %5.1f, \t %5.1f, \t %5.3f", 
+					crop.getGamsName(), minTrade, maxTrade, importPrice, exportPrice, imports, exports, ruminantFeed, monogastricFeed, seedAndWasteRate));
+			
+			setGamsParamValue(minTradeP.addRecord(crop.getGamsName()), minTrade, 3);
+			setGamsParamValue(maxTradeP.addRecord(crop.getGamsName()), maxTrade, 3);
+			setGamsParamValue(importPriceP.addRecord(crop.getGamsName()), importPrice, 3);
+			setGamsParamValue(exportPriceP.addRecord(crop.getGamsName()), exportPrice, 3);
+			setGamsParamValue(previousImportAmountP.addRecord(crop.getGamsName()), imports, 3);
+			setGamsParamValue(previousExportAmountP.addRecord(crop.getGamsName()), exports, 3);
+			setGamsParamValue(previousRuminantFeedP.addRecord(crop.getGamsName()), ruminantFeed, 3);
+			setGamsParamValue(previousMonogastricFeedP.addRecord(crop.getGamsName()), monogastricFeed, 3);
+			setGamsParamValue(seedAndWasteRateP.addRecord(crop.getGamsName()), seedAndWasteRate, 3);
+		}
+		
+
+		//below in case running without shocks to use original values in model config 
+		
+		double meatEff = ModelConfig.updateParameterForShocks(inputData.getTimestep().getYear(), "MEAT_EFFICIENCY");
+		double fertCost = ModelConfig.updateParameterForShocks(inputData.getTimestep().getYear(), "FERTILISER_COST_PER_T");
+		double otherIntCost = ModelConfig.updateParameterForShocks(inputData.getTimestep().getYear(), "OTHER_INTENSITY_COST");
+
+		LogWriter.print("\n");	
+		addScalar(inDB, "meatEfficency", meatEff, 3);		
+		addScalar(inDB, "fertiliserUnitCost", fertCost, 3);
+		addScalar(inDB, "otherICost",otherIntCost, 3);
+		addScalar(inDB, "otherIParam", ModelConfig.OTHER_INTENSITY_PARAM, 3);
+		addScalar(inDB, "unhandledCropRate", ModelConfig.UNHANDLED_CROP_RATE, 3);
+		addScalar(inDB, "setAsideRate", ModelConfig.SETASIDE_RATE, 5);
+		addScalar(inDB, "domesticPriceMarkup", ModelConfig.DOMESTIC_PRICE_MARKUP, 3);
+		double maxExpansion = 1.0;
+		if (!ModelConfig.IS_CALIBRATION_RUN && countryInput.getCountry().getName().equals("China")) {
+			maxExpansion = ModelConfig.MAX_CHINA_LAND_EXPANSION_RATE;
+		}
+		addScalar(inDB, "maxLandExpansionRate", maxExpansion, 3);
+		
+		addScalar(inDB, "carbonPrice", countryInput.getCarbonPrice().getExportPrice(), 5);
+		addScalar(inDB, "woodExportPrice", countryInput.getWoodPrice().getExportPrice(), 5);
+		addScalar(inDB, "woodImportPrice", countryInput.getWoodPrice().getImportPrice(), 5);
+		
+		// Not simulating production of different wood products so need to aggregate
+		double woodMinNetImport = 0;
+		double woodMaxNetImport = 0;
+		Map<WoodType, TradeConstraint> woodTradeConstraints = countryInput.getWoodTradeConstraints();
+		for (TradeConstraint tc: woodTradeConstraints.values()) {
+			woodMinNetImport += tc.getMinConstraint();
+			woodMaxNetImport += tc.getMaxConstraint();
+		}
+		
+		addScalar(inDB, "woodMaxNetImport", woodMaxNetImport, 5);
+		addScalar(inDB, "woodMinNetImport", woodMinNetImport, 5);
+		
+		// Wood demand
+		Map<WoodType, Double> woodDemandMap = countryInput.getWoodDemand();
+		double totalWoodDemand = woodDemandMap.values().stream().reduce(0.0, Double::sum);
+		addScalar(inDB, "woodDemand", totalWoodDemand, 5);		
+		
+		// Carbon fluxes
+		GAMSParameter cFluxRateP = inDB.addParameter("carbonFluxRateLUC", 3);
+		GAMSParameter cFluxRateNeeP = inDB.addParameter("carbonFluxRateNEE", 2);
+		
+		for (Entry<Integer, ? extends CarbonFluxItem> entry : inputData.getCarbonFluxes().entrySet()) {
+			Integer locationId = entry.getKey();
+			String locString = Integer.toString(locationId);
+			CarbonFluxItem cFlux = entry.getValue();
+			
+			for (Map.Entry<LccKey, Double> cfEntry : cFlux.getConversionCarbonFluxMap().entrySet()) {
+				LccKey key = cfEntry.getKey();
+				Vector<String> v = new Vector<String>();
+				v.add(key.getFromLc().getName());
+				v.add(key.getToLc().getName());
+				v.add(locString);
+				setGamsParamValue(cFluxRateP.addRecord(v), cfEntry.getValue(), 2);
+			}
+			
+			for (Map.Entry<LandCoverType, Double> cfEntry : cFlux.getCarbonHorizonFluxMap().entrySet()) {
+				Vector<String> v = new Vector<String>();
+				v.add(cfEntry.getKey().getName());
+				v.add(locString);
+				setGamsParamValue(cFluxRateNeeP.addRecord(v), cfEntry.getValue(), 3);
+			}	
+		}
+		
+		// Yield from timber forest rotation
+		Map<Integer, ? extends WoodYieldData> woodYieldData = inputData.getWoodYields();
+		
+		GAMSParameter woodYieldRotaP = inDB.addParameter("woodYieldRota", 1);
+		GAMSParameter woodYieldLuc = inDB.addParameter("woodYieldLUC", 3);
+		
+		for (Entry<Integer, ? extends WoodYieldData> entry : woodYieldData.entrySet()) {
+			Integer locationId = entry.getKey();
+			String locString = Integer.toString(locationId);
+			WoodYieldData wYield = entry.getValue();
+
+			Vector<String> v = new Vector<String>();
+			v.add(locString);
+			setGamsParamValue(woodYieldRotaP.addRecord(v), wYield.getYieldRota(), 5);
+			
+			for (LandCoverType toLc : LandCoverType.getAgriculturalTypes()) {
+				Vector<String> w = new Vector<String>();
+				w.add(LandCoverType.NATURAL.getName());
+				w.add(toLc.getName());
+				w.add(locString);
+				setGamsParamValue(woodYieldLuc.addRecord(w), wYield.getYieldLuc(LandCoverType.NATURAL, toLc), 5);
+			}
+
+		}	
+		
+		// Land cover conversion cost
+		GAMSParameter conversionCostP = inDB.addParameter("conversionCost", 2);
+		Map<LccKey, Double> conversionCosts = inputData.getConversionCosts();
+		
+		for (Map.Entry<LccKey, Double> entry : conversionCosts.entrySet()) {
+			LccKey key = entry.getKey();
+			String fromName = key.getFromLc().getName();
+			String toName = key.getToLc().getName();
+			double cost = entry.getValue();
+			Vector<String> v = new Vector<String>();
+			v.add(fromName);
+			v.add(toName);
+			setGamsParamValue(conversionCostP.addRecord(v), cost, 5);
+		}
+		
+		GAMSParameter forestRotationP = inDB.addParameter("forestRotationPeriod", 1);
+		for (Map.Entry<Integer, Double> entry : inputData.getRotationPeriods().entrySet()) {
+			int locationId = entry.getKey();
+			double rotPeriod = entry.getValue();
+			String locString = Integer.toString(locationId);
+			setGamsParamValue(forestRotationP.addRecord(locString), rotPeriod, 0);
+		}
+		
+		addScalar(inDB, "forestEstablishmentCost", ModelConfig.FOREST_ESTABLISHMENT_COST, 5);
+		addScalar(inDB, "vegClearingCostRate", ModelConfig.VEGETATION_CLEARING_COST, 5);
+	}
+
+	private void addScalar(GAMSDatabase gamsDb, String recordName, double val, int places) {
+		GAMSParameter param = gamsDb.addParameter(recordName, 0);
+		double dOut = setGamsParamValue(param.addRecord(), val, places);
+		if (DEBUG) LogWriter.println(recordName + ": " + dOut);
+	}
+
+	private double setGamsParamValue(GAMSParameterRecord param, double val, int places) {
+		double dOut = places >= 0 ? Math.round(val * Math.pow(10,places)) / Math.pow(10,places) : val;
+		param.setValue(dOut);
+		return dOut;
+	}
+	
+	private double setGamsParamValueTruncate(GAMSParameterRecord param, double val, int places) {
+		double dOut = ((int)(val * Math.pow(10,places))) / Math.pow(10,places);
+		param.setValue(dOut);
+		return dOut;
+	}
+	
+	@SuppressWarnings("serial")
+	private GamsLocationOutput handleResults(GAMSDatabase outDB) {
+		int modelStatusInt = (int) outDB.getParameter("ms").findRecord().getValue();
+		ModelStat modelStatus = GAMSGlobals.ModelStat.lookup(modelStatusInt);
+		String contextString = String.format("%s %s: Modelstatus %s, Solvestatus %s", 
+    			inputData.getCountryInput().getCountry(),
+			inputData.getTimestep().getYear(),
+			modelStatus.toString(),
+			GAMSGlobals.SolveStat.lookup((int) outDB.getParameter("ss").findRecord().getValue()));
+        LogWriter.println("\n" + contextString);
+
+		if (modelStatus != ModelStat.OPTIMAL_LOCAL) {
+			LogWriter.printlnError("Critical!!! Land use incorrectly solved. " + contextString);
+		}
+		
+		GAMSVariable varAreas = outDB.getVariable("cropArea");
+		GAMSVariable varFertIntensities = outDB.getVariable("fertI");
+		GAMSVariable varIrrigIntensities = outDB.getVariable("irrigI");
+		GAMSVariable varOtherIntensities = outDB.getVariable("otherIntensity");
+		GAMSVariable varRuminantFeed = outDB.getVariable("ruminantFeed");
+		GAMSVariable varMonogastricFeed = outDB.getVariable("monogastricFeed");
+		GAMSParameter parmNetImports = outDB.getParameter("netImportAmount");
+		GAMSParameter parmNetImportCost = outDB.getParameter("netImportCost");
+		GAMSVariable varYields = outDB.getVariable("yield");
+		GAMSVariable varUnitEnergies = outDB.getVariable("unitCost");
+		GAMSParameter parmProd = outDB.getParameter("totalProd");
+		GAMSParameter parmProdCost = outDB.getParameter("totalProdCost");
+		GAMSParameter parmCroplandArea = outDB.getParameter("totalCropland");
+		GAMSParameter parmTotalArea = outDB.getParameter("totalArea");
+		GAMSParameter parmProdShock = outDB.getParameter("productionShock");		
+		
+		double totalCropArea = 0;
+		double totalPastureArea = 0;
+		double area, fertIntensity, irrigIntensity, otherIntensity = Double.NaN, ruminantFeed, monogastricFeed, netImport, netImportCost, yield, unitCost, prod, prodCost;	
+		
+		final LazyHashMap<Integer, LandUseItem> landUses = new LazyHashMap<Integer, LandUseItem>() { 
+			protected LandUseItem createValue() { return new LandUseItem(); }
+		};		
+		
+		Map<Integer, ? extends IrrigationItem> allIrrigationRefData = inputData.getIrrigationCosts();
+
+		Map<CropType, CropUsageData> cropUsageData = new HashMap<CropType, CropUsageData>();
+
+		for (GAMSVariableRecord rec : varAreas) {
+			String itemName = rec.getKeys()[0];
+			String locationName = rec.getKeys()[1];
+			area = rec.getLevel();
+			fertIntensity = varFertIntensities.findRecord(itemName, locationName).getLevel();
+			irrigIntensity = varIrrigIntensities.findRecord(itemName, locationName).getLevel();
+			otherIntensity = varOtherIntensities.findRecord(itemName, locationName).getLevel();
+			yield = varYields.findRecord(itemName, locationName).getLevel();
+			unitCost = varUnitEnergies.findRecord(itemName, locationName).getLevel();
+
+			int locId = Integer.parseInt(locationName);
+			CropType cropType = CropType.getForGamsName(itemName);
+
+			if (!cropUsageData.containsKey(cropType)) {  // then we must not have seen this crop type before, so need to do all non location specific stuff
+				ruminantFeed = varRuminantFeed.findRecord(itemName).getLevel();
+				monogastricFeed = varMonogastricFeed.findRecord(itemName).getLevel();
+				netImport = cropType.isImportedCrop() ? getParmValue(parmNetImports, itemName) : 0;
+				netImportCost = cropType.isImportedCrop() ? getParmValue(parmNetImportCost, itemName) : 0;
+				prod =  getParmValue(parmProd, itemName);
+				prodCost = getParmValue(parmProdCost, itemName);
+				double totalArea = getParmValue(parmTotalArea, itemName);
+				double prodShock = getParmValue(parmProdShock, itemName);
+
+				cropUsageData.put(cropType, new CropUsageData(ruminantFeed, monogastricFeed, netImport, netImportCost, prod, prodCost, totalArea, prodShock));
+				if (DEBUG) LogWriter.println(String.format("\n%s:\tarea= %.1f,\tmonogastricFeed= %.1f,\truminantFeed= %.1f,\tnetImports= %.3f,\tnetImportCost= %.3f,\tprod= %.3f, \tprodCost= %.3f,\tprodCostRate= %.3f,\tprodShock= %.3f", itemName, totalArea, monogastricFeed, ruminantFeed, netImport, netImportCost, prod, prodCost, prodCost/prod, prodShock)); 
+			
+			}
+
+			LandUseItem landUseItem = landUses.lazyGet(locId); // returns landUseItem for location. If does not exist, creates new one
+
+			if (area > 0) { 
+
+				if (DEBUG) LogWriter.println(String.format("\t location %s, %s:\tarea= %.1f,\tfert= %.3f,\tirrg= %.3f,\tintensity= %.3f", locationName, itemName, area, fertIntensity, irrigIntensity, otherIntensity));				
+				IrrigationItem irrigRefData = allIrrigationRefData.get(locId);
+				landUseItem.setIntensity(cropType, new Intensity(fertIntensity, irrigIntensity, otherIntensity, yield, unitCost, irrigRefData.getMaxIrrigAmount(cropType)));
+			}
+			
+			
+			double croplandArea = getParmValue(parmCroplandArea, locationName);
+
+			landUseItem.setCropFraction(cropType, croplandArea > 0 ? area/croplandArea : 0); // TODO gives wrong pasture numbers
+			
+		}
+
+		for (CropType meatTypes : CropType.getMeatTypes()) {
+			netImport = getParmValue(parmNetImports, meatTypes.getGamsName());
+			netImportCost= getParmValue(parmNetImportCost, meatTypes.getGamsName());
+			prod = getParmValue(parmProd, meatTypes.getGamsName());
+			prodCost = getParmValue(parmProdCost, meatTypes.getGamsName());
+
+			cropUsageData.put(meatTypes, new CropUsageData(0.0, 0.0, netImport, netImportCost, prod, prodCost, Double.NaN, 0));
+			if (DEBUG) LogWriter.println(String.format("\n%s:\t\t\t\t\tnetImports= %.3f,\tnetImportCost= %.3f,\tprod= %.3f,\tprodCost= %.3f", meatTypes.getGamsName(), netImport, netImportCost, prod, prodCost)); 
+		}
+		LogWriter.println(String.format("\n%s %s: Total area= %.1f (crop=%.1f, pasture %.1f)", 
+				inputData.getCountryInput().getCountry(), inputData.getTimestep().getYear(), 
+				totalCropArea+totalPastureArea, totalCropArea, totalPastureArea));
+			
+		// Land cover change
+		GAMSVariable varLandCoverChange = outDB.getVariable("landCoverChange");
+		Map<Integer, ArrayList<LandCoverChangeItem>> landCoverChanges = new HashMap<Integer, ArrayList<LandCoverChangeItem>>();
+		
+		for (GAMSVariableRecord rec : varLandCoverChange) {
+			String fromLcStr = rec.getKeys()[0];
+			String toLcStr = rec.getKeys()[1];
+			String locationName = rec.getKeys()[2];
+			int locId = Integer.parseInt(locationName);
+			double change = rec.getLevel();
+			
+			// Skip if no change or no conversion
+			if (change == 0 || fromLcStr.equals(toLcStr)) 
+				continue;
+
+			LandCoverType fromLc = LandCoverType.getForName(fromLcStr);
+			LandCoverType toLc = LandCoverType.getForName(toLcStr);	
+			
+			ArrayList<LandCoverChangeItem> changesList = landCoverChanges.computeIfAbsent(locId, k -> new ArrayList<LandCoverChangeItem>());
+			changesList.add(new LandCoverChangeItem(fromLc, toLc, change));
+		}
+
+		// Carbon flux
+		double netCarbonFlux = outDB.getParameter("netCarbonFlux").getFirstRecord().getValue();
+		
+		// Timber harvest
+		double totalWoodHarvest = outDB.getParameter("totalWoodHarvest").getFirstRecord().getValue();
+		double netWoodImport = outDB.getParameter("netWoodImport").getFirstRecord().getValue();
+		double lucHarvest = outDB.getVariable("woodSupplyLUC").getFirstRecord().getLevel();
+		
+		// Need to disaggregate wood harvest and import by type. This is just for future proofing. Eventually, could have separate harvest for each wood type.
+		Map<WoodType, WoodUsageData> newWoodUsageMap = new HashMap<WoodType, WoodUsageData>();
+		Map<WoodType, WoodUsageData> previousWoodUsageMap = inputData.getCountryInput().getPreviousWoodUsageData();
+		double previousNetImportSum = previousWoodUsageMap.values().stream().mapToDouble(o -> o.getNetImport()).sum();
+		double netWoodImportChange = netWoodImport - previousNetImportSum;
+		double previousWoodHarvestSum = previousWoodUsageMap.values().stream().mapToDouble(o -> o.getHarvest()).sum();
+		double previousLucHavrestSum = previousWoodUsageMap.values().stream().mapToDouble(o -> o.getLucHarvest()).sum();
+		int numWoodTypes = WoodType.values().length;
+		for (WoodType wt : WoodType.values()) {
+			double previousNetImport = previousWoodUsageMap.get(wt).getNetImport();
+			double newNetImport = previousNetImport + netWoodImportChange / numWoodTypes;
+			double previousHarvest = previousWoodUsageMap.get(wt).getHarvest();
+			double previousLucHarvest = previousWoodUsageMap.get(wt).getLucHarvest();
+			// assuming equal split if no previous harvest
+			double newHarvest = (previousWoodHarvestSum > 0) ? totalWoodHarvest * (previousHarvest / previousWoodHarvestSum) : totalWoodHarvest / numWoodTypes;
+			double newLucHarvest = (previousLucHavrestSum > 0) ? lucHarvest * (previousLucHarvest / previousLucHavrestSum) : lucHarvest / numWoodTypes;
+			newWoodUsageMap.put(wt, new WoodUsageData(newHarvest, newNetImport, newLucHarvest));
+		}
+
+		GamsLocationOutput results = new GamsLocationOutput(modelStatus, landUses, cropUsageData, landCoverChanges, netCarbonFlux, newWoodUsageMap);
+		return results;
+	}
+
+	private double getParmValue(GAMSParameter aParm, String itemName) {
+		try {
+			GAMSParameterRecord record = aParm.findRecord(itemName);
+			double d = record.getValue();
+			return d;
+		}
+		catch (GAMSException gamsEx) {
+			//LogWriter.println("GAMSException thrown for " + itemName);
+			return 0;
+		}
+	}
+
+	private void addCommodityMapParm(GAMSParameter parm, Map<CommodityType, Double> itemMap, int places) {
+		for (Map.Entry<CommodityType, Double> entry : itemMap.entrySet()) {
+			double d = entry.getValue();
+			if (DEBUG) LogWriter.println(String.format("     %15s,\t %.1f", entry.getKey().getGamsName(), d));
+			if (!Double.isNaN(d))
+				setGamsParamValue(parm.addRecord(entry.getKey().getGamsName()), d, places);
+		}
+	}
+
+
+	private void cleanup(String directory)  {
+		File directoryToDelete = new File(directory);
+		String files[] = directoryToDelete.list();
+		for (String file : files) {
+			File fileToDelete = new File(directoryToDelete, file);
+			try {
+				fileToDelete.delete();
+			} catch(Exception e){
+				LogWriter.print(e);
+			}
+		}
+		try {
+			directoryToDelete.delete();
+		} catch(Exception e) {
+			LogWriter.print(e);
+		}
+	}
+}
diff --git a/src/ac/ed/lurg/country/gams/GamsLocationOutput.java b/src/ac/ed/lurg/country/gams/GamsLocationOutput.java
index 08bb3f85a592941e798f553c7413daea5e7eac2e..5b2d3b4ab47d2355cc7e65d3210cf21d5b53e397 100644
--- a/src/ac/ed/lurg/country/gams/GamsLocationOutput.java
+++ b/src/ac/ed/lurg/country/gams/GamsLocationOutput.java
@@ -1,67 +1,60 @@
-package ac.ed.lurg.country.gams;
-
-import java.util.ArrayList;
-import java.util.Map;
-
-import com.gams.api.GAMSGlobals.ModelStat;
-
-import ac.ed.lurg.landuse.CropUsageData;
-import ac.ed.lurg.landuse.LandUseItem;
-import ac.ed.lurg.landuse.WoodUsageData;
-import ac.ed.lurg.types.CropType;
-import ac.ed.lurg.types.WoodType;
-import ac.ed.lurg.country.LandCoverChangeItem;
-
-public class GamsLocationOutput {
-	ModelStat status;
-	
-	Map<Integer, LandUseItem> landUses;  // data mapped from id (not raster)
-	private Map<CropType, CropUsageData> cropUsageData;
-	Map<Integer, ArrayList<LandCoverChangeItem>> landCoverChanges;
-	private double netCarbonFlux;
-	private Map<WoodType, WoodUsageData> woodUsageData;
-	private Map<Integer, Double> naturalForestCleared;
-	
-	public GamsLocationOutput(ModelStat status, 
-			Map<Integer, LandUseItem> landUses, 
-			Map<CropType, CropUsageData> cropUsageData,
-			Map<Integer, ArrayList<LandCoverChangeItem>> landCoverChanges,
-			double netCarbonFlux, Map<WoodType, WoodUsageData> woodUsageData,
-			Map<Integer, Double> naturalForestCleared) {
-		super();
-		this.status = status;
-		this.landUses = landUses;
-		this.cropUsageData = cropUsageData;
-		this.landCoverChanges = landCoverChanges;
-		this.netCarbonFlux = netCarbonFlux;
-		this.woodUsageData = woodUsageData;
-		this.naturalForestCleared = naturalForestCleared;
-	}
-	
-	public ModelStat getStatus() {
-		return status;
-	}
-	public Map<Integer, LandUseItem> getLandUses() {
-		return landUses;
-	}
-
-	public Map<CropType, CropUsageData> getCommoditiesData() {
-		return cropUsageData;
-	}
-	
-	public Map<Integer, ArrayList<LandCoverChangeItem>> getLandCoverChanges() {
-		return landCoverChanges;
-	}
-
-	public double getNetCarbonFlux() {
-		return netCarbonFlux;
-	}
-
-	public Map<WoodType, WoodUsageData> getWoodUsageData() {
-		return woodUsageData;
-	}
-
-	public Map<Integer, Double> getNaturalForestCleared() {
-		return naturalForestCleared;
-	}
-}
+package ac.ed.lurg.country.gams;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+import com.gams.api.GAMSGlobals.ModelStat;
+
+import ac.ed.lurg.landuse.CropUsageData;
+import ac.ed.lurg.landuse.LandUseItem;
+import ac.ed.lurg.landuse.WoodUsageData;
+import ac.ed.lurg.types.CropType;
+import ac.ed.lurg.types.WoodType;
+import ac.ed.lurg.country.LandCoverChangeItem;
+
+public class GamsLocationOutput {
+	ModelStat status;
+	
+	Map<Integer, LandUseItem> landUses;  // data mapped from id (not raster)
+	private Map<CropType, CropUsageData> cropUsageData;
+	Map<Integer, ArrayList<LandCoverChangeItem>> landCoverChanges;
+	private double netCarbonFlux;
+	private Map<WoodType, WoodUsageData> woodUsageData;
+	
+	public GamsLocationOutput(ModelStat status, 
+			Map<Integer, LandUseItem> landUses, 
+			Map<CropType, CropUsageData> cropUsageData,
+			Map<Integer, ArrayList<LandCoverChangeItem>> landCoverChanges,
+			double netCarbonFlux, Map<WoodType, WoodUsageData> woodUsageData) {
+		super();
+		this.status = status;
+		this.landUses = landUses;
+		this.cropUsageData = cropUsageData;
+		this.landCoverChanges = landCoverChanges;
+		this.netCarbonFlux = netCarbonFlux;
+		this.woodUsageData = woodUsageData;
+	}
+	
+	public ModelStat getStatus() {
+		return status;
+	}
+	public Map<Integer, LandUseItem> getLandUses() {
+		return landUses;
+	}
+
+	public Map<CropType, CropUsageData> getCommoditiesData() {
+		return cropUsageData;
+	}
+	
+	public Map<Integer, ArrayList<LandCoverChangeItem>> getLandCoverChanges() {
+		return landCoverChanges;
+	}
+
+	public double getNetCarbonFlux() {
+		return netCarbonFlux;
+	}
+
+	public Map<WoodType, WoodUsageData> getWoodUsageData() {
+		return woodUsageData;
+	}
+}
diff --git a/src/ac/ed/lurg/country/gams/GamsRasterOptimiser.java b/src/ac/ed/lurg/country/gams/GamsRasterOptimiser.java
index 9350524b75636f6804b27df741e132f3159582a2..a754cc60412f9ff60461fa6dba979650fd4e4094 100644
--- a/src/ac/ed/lurg/country/gams/GamsRasterOptimiser.java
+++ b/src/ac/ed/lurg/country/gams/GamsRasterOptimiser.java
@@ -1,457 +1,453 @@
-package ac.ed.lurg.country.gams;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
-import ac.ed.lurg.ModelConfig;
-import ac.ed.lurg.carbon.CarbonFluxItem;
-import ac.ed.lurg.country.LandCoverChangeItem;
-import ac.ed.lurg.forestry.WoodYieldData;
-import ac.ed.lurg.forestry.WoodYieldItem;
-import ac.ed.lurg.landuse.Intensity;
-import ac.ed.lurg.landuse.IrrigationItem;
-import ac.ed.lurg.landuse.LandUseItem;
-import ac.ed.lurg.landuse.LccKey;
-import ac.ed.lurg.types.CropType;
-import ac.ed.lurg.types.LandCoverType;
-import ac.ed.lurg.types.YieldType;
-import ac.ed.lurg.utils.LazyTreeMap;
-import ac.ed.lurg.utils.LogWriter;
-import ac.ed.lurg.yield.YieldRaster;
-import ac.ed.lurg.yield.YieldResponsesItem;
-import ac.sac.raster.IntegerRasterItem;
-import ac.sac.raster.RasterKey;
-import ac.sac.raster.RasterSet;
-
-public class GamsRasterOptimiser {
-	public static final boolean DEBUG = false;
-
-	private GamsRasterInput rasterInputData;
-	private RasterSet<IntegerRasterItem> mapping;
-
-	public GamsRasterOptimiser(GamsRasterInput rasterInputData, RasterSet<IntegerRasterItem> clusterMapping) {
-		this.rasterInputData = rasterInputData;
-		this.mapping = clusterMapping;
-	}
-
-	public GamsRasterOutput run() {
-		// workout similar areas		
-		GamsLocationInput gamsInput = convertFromRaster(rasterInputData);
-
-		long start = System.currentTimeMillis();
-
-		GamsLocationOptimiser opti = new GamsLocationOptimiser(gamsInput);		
-		GamsLocationOutput gamsOutput = opti.run();
-
-		long last = System.currentTimeMillis();
-		LogWriter.println("Took " + (last-start));
-
-		// map results back to raster
-		return convertToRaster(gamsInput, gamsOutput);
-	}
-
-	private GamsRasterOutput convertToRaster(GamsLocationInput gamsInput, GamsLocationOutput gamsOutput) {		
-		RasterSet<LandUseItem> newIntensityRaster = allocAreas(gamsInput.getPreviousLandUse(), gamsOutput, gamsInput.getTimestep().getYear());
-
-		return new GamsRasterOutput(gamsOutput.getStatus(), newIntensityRaster, gamsOutput.getCommoditiesData(),
-				gamsOutput.getNetCarbonFlux(), gamsOutput.getWoodUsageData());
-	}
-
-	private RasterSet<LandUseItem> createWithSameLandCovers(RasterSet<LandUseItem> toCopy) {
-		RasterSet<LandUseItem> theCopy = new RasterSet<LandUseItem>(toCopy.getHeaderDetails()); // TODO why do we make a copy???
-
-		for (Entry<RasterKey, LandUseItem> entry : toCopy.entrySet()) {
-			if (entry.getValue() != null) {
-				LandUseItem newAreasItem =  new LandUseItem(entry.getValue());
-				theCopy.put(entry.getKey(), newAreasItem);
-			}
-		}
-
-		return theCopy;
-	}
-
-	private void checkedTotalAreas(Map<? extends Object, LandUseItem> areaRaster, String comment) {
-		for (LandCoverType l : LandCoverType.values()) {
-			double total = 0;
-			double unprotected = 0;
-			for (LandUseItem a : areaRaster.values()) {
-				if (a != null) {
-					total += a.getConvertibleLandCoverArea(l) + a.getProtectedLandCoverArea(l);
-					unprotected += a.getConvertibleLandCoverArea(l);
-				}
-										
-			}
-
-			LogWriter.println("Total Area " + comment + ": " + l.getName() + ": total = " + total + ", unprotected = " + unprotected);
-		}
-
-		double suitableArea=0, protectedArea=0;
-		for (LandUseItem a : areaRaster.values()) {
-			if (a != null) {
-				protectedArea += a.getTotalProtectedArea();
-				suitableArea += a.getSuitableArea();
-			}
-		}
-		LogWriter.println("Total protectedArea " + comment + ": " + protectedArea);
-		LogWriter.println("Total suitableArea " + comment + ": " + suitableArea);
-	}
-
-	private RasterSet<LandUseItem> allocAreas(Map<Integer, ? extends LandUseItem> prevAreasAgg, GamsLocationOutput gamsOutput, int year) {
-		RasterSet<LandUseItem> newLandUseRaster = createWithSameLandCovers(rasterInputData.getPreviousLandUses());
-
-		for (Map.Entry<Integer, LandUseItem> entry : gamsOutput.getLandUses().entrySet()) {
-			Integer locId = entry.getKey();
-			LandUseItem newLandUseAggItem = entry.getValue();
-
-			Set<RasterKey> keys = new HashSet<RasterKey>();
-			for (Entry<RasterKey, IntegerRasterItem> mapEntry : mapping.entrySet()) {
-				IntegerRasterItem iri = mapEntry.getValue();
-
-				if (iri != null && locId == iri.getInt())
-					keys.add(mapEntry.getKey());
-			}
-			
-			RasterSet<LandUseItem> landUseItemsForLocation = newLandUseRaster.createSubsetForKeys(keys);
-			
-			ArrayList<LandCoverChangeItem> landCoverChanges = gamsOutput.getLandCoverChanges().get(locId);
-			
-			if (landCoverChanges != null) {	
-				// Allocate land cover change to grid cells
-				for (LandCoverChangeItem lccItem : landCoverChanges) {
-					LandCoverType fromLC = lccItem.getFromLandCover();
-					LandCoverType toLC = lccItem.getToLandCover();
-					double change = lccItem.getArea();
-					allocAllLandCropsTop(newLandUseRaster, keys, toLC, fromLC, change, locId);
-					
-				}
-			}
-			
-			// Deforested natural areas. No change in natural area but forest stand area decreased and new stand created
-			if (gamsOutput.getNaturalForestCleared().containsKey(locId)) {
-				double natForestCleared = gamsOutput.getNaturalForestCleared().get(locId);
-
-				allocAllLandCropsTop(newLandUseRaster, keys, LandCoverType.NATURAL, LandCoverType.NATURAL, natForestCleared, locId);
-			}
-		
-
-			for (RasterKey key : keys) {
-				LandUseItem newLandUseItem = newLandUseRaster.get(key);
-				if (newLandUseItem != null) {
-					for (CropType crop : CropType.values()) {
-						newLandUseItem.setCropFraction(crop, newLandUseAggItem.getCropFraction(crop));
-						newLandUseItem.setIntensity(crop, newLandUseAggItem.getIntensity(crop)); // intensities constant over single aggregated land category
-					}
-				}
-			}
-			
-			if (DEBUG) checkedTotalAreas(landUseItemsForLocation, locId + " after");
-		}
-
-		return newLandUseRaster;
-	}
-
-	private void allocAllLandCropsTop(RasterSet<LandUseItem> newLandUseRaster, Set<RasterKey> keys, LandCoverType toLC, LandCoverType fromLC, double change, Integer locId) {
-		LandCoverType toType = toLC;
-		LandCoverType fromType = fromLC;
-
-		// reverse direction if negative
-		if (change < 0) {
-			change = -change;
-			toType = fromLC;
-			fromType = toLC;
-		}
-
-		double shortfall = allocAllLandCrops(newLandUseRaster, keys, toType, fromType, change);
-
-		if (shortfall > 0.00001) {
-			LogWriter.printlnError("This should never happen, due to GAMS constraint. Not able to incorporate all changes: from " + fromLC + " to " + toLC + " " + locId + ": " + shortfall);
-		}
-	}
-
-	private double allocAllLandCrops(RasterSet<LandUseItem> newLandUseRaster, Set<RasterKey> keys, LandCoverType toType, LandCoverType fromType, double change) {
-		if (DEBUG) LogWriter.println("allocAllLandCrops: from " + fromType + " to " + toType + " change " + change);
-		if (change < 0.00001)
-			return 0;
-
-		double totalShortfall = 0;
-		Set<RasterKey> keysWithSpace = new HashSet<RasterKey>();
-		double totalUnprotectedLC = 0;
-		
-		for (RasterKey key : keys) {
-			LandUseItem newLandUseItem = newLandUseRaster.get(key);
-			totalUnprotectedLC += newLandUseItem.getConvertibleLandCoverArea(fromType);
-		}
-
-		for (RasterKey key : keys) {
-			//if (DEBUG) LogWriter.println("Processing raster key " + key);
-			LandUseItem newLandUseItem = newLandUseRaster.get(key);
-
-			if (newLandUseItem!=null) {
-				double cellChange = (totalUnprotectedLC > 0) ? change * newLandUseItem.getConvertibleLandCoverArea(fromType)/totalUnprotectedLC : change / keys.size();
-				double shortfall = newLandUseItem.moveAreas(toType, fromType, cellChange);
-				if (shortfall == 0)
-					keysWithSpace.add(key);
-				else
-					totalShortfall += shortfall;
-				
-				//LogWriter.println(key.toString() + "|" + cellChange);
-			}
-		}
-
-		if (DEBUG) LogWriter.println("allocAllLandCrops: from " + fromType + " to " + toType + " change " + change + " totalShortfall " + totalShortfall);
-		return totalShortfall;
-	}
-
-	@SuppressWarnings("serial")
-	private GamsLocationInput convertFromRaster(GamsRasterInput rasterInputData) {
-		// as a first attempt only going to look at pasture and cereal yields, assuming other yields will be correlated to one or the other.
-		YieldRaster yieldRaster = rasterInputData.getYields();
-		RasterSet<LandUseItem> landUseRaster = rasterInputData.getPreviousLandUses();
-
-		RasterSet<IrrigationItem> irrigRaster = rasterInputData.getIrrigationData();
-		
-		RasterSet<CarbonFluxItem> carbonFluxRaster = rasterInputData.getCarbonFluxes();
-		
-		RasterSet<WoodYieldItem> woodYieldRaster = rasterInputData.getWoodYields();
-
-		// look for inconsistencies
-		for (Entry<RasterKey, YieldResponsesItem> entry : yieldRaster.entrySet()) {
-
-			YieldResponsesItem yresp = entry.getValue();
-			RasterKey key = entry.getKey();
-			CropType crop = CropType.WHEAT;
-
-			if (yresp != null && (yresp.getYield(YieldType.FERT_MAX_IRRIG_MAX, crop) < yresp.getYield(YieldType.FERT_MAX_NO_IRRIG, crop) || yresp.getYield(YieldType.FERT_MAX_IRRIG_MAX, crop) < yresp.getYield(YieldType.NO_FERT_IRRIG_MAX, crop))) {
-				logWarningWithCoord("Inconsistency F only:" + yresp.getYield(YieldType.FERT_MAX_NO_IRRIG, crop) + ", I only" + yresp.getYield(YieldType.NO_FERT_IRRIG_MAX, crop) + ", max " + yresp.getYield(YieldType.FERT_MAX_IRRIG_MAX, crop) + " at ", key, yieldRaster);
-			}
-		}
-
-		// Aggregation
-		LazyTreeMap<Integer, YieldResponsesItem> aggregatedYields = new LazyTreeMap<Integer, YieldResponsesItem>() { 
-			protected YieldResponsesItem createValue() { return new YieldResponsesItem(); }
-		};
-		LazyTreeMap<Integer, LandUseItem> aggregatedAreas = new LazyTreeMap<Integer, LandUseItem>() { 
-			protected LandUseItem createValue() { return new LandUseItem(); }
-		};
-		LazyTreeMap<Integer, IrrigationItem> aggregatedIrrigCosts = new LazyTreeMap<Integer, IrrigationItem>() { 
-			protected IrrigationItem createValue() { return new IrrigationItem(); }
-		};
-		LazyTreeMap<Integer, CarbonFluxItem> aggregatedCarbonFluxes = new LazyTreeMap<Integer, CarbonFluxItem>() { 
-			protected CarbonFluxItem createValue() { return new CarbonFluxItem(); }
-		};
-		LazyTreeMap<Integer, WoodYieldData> aggregatedWoodYields = new LazyTreeMap<Integer, WoodYieldData>() { 
-			protected WoodYieldData createValue() { return new WoodYieldData(); }
-		};		
-		Map<Integer, Double> aggregatedForestRotation = new HashMap<Integer, Double>();
-
-		int yRespFound = 0, yRespMissing = 0;
-		int mappingsFound = 0, mappingsMissing = 0;
-
-		for (RasterKey key : landUseRaster.keySet()) {
-			if (mapping.get(key) == null) {
-				mappingsMissing++;
-				continue;
-			} else {
-				mappingsFound++;
-			}
-			
-			int clusterId = mapping.get(key).getInt();
-			
-			YieldResponsesItem yresp = yieldRaster.get(key);
-			if (yresp == null) {
-				yRespMissing++;
-				//logErrorWithCoord("No YieldResponsesItem for ", key, yieldRaster);
-			}
-			else {
-				yRespFound++;
-				//logErrorWithCoord("YieldResponsesItem found for ", key, yieldRaster);
-			}
-			
-			LandUseItem landUseItem  = landUseRaster.get(key);
-			if (landUseItem == null) {
-				LogWriter.printlnError("GamsRasterOptimiser: Can't find landUseItem for " + key);
-				continue;
-			}
-
-			IrrigationItem irrigItem = irrigRaster.get(key);
-			WoodYieldItem woodYieldItem = woodYieldRaster.get(key);
-			CarbonFluxItem carbonFluxItem = carbonFluxRaster.get(key);
-
-			YieldResponsesItem aggYResp = aggregatedYields.lazyGet(clusterId);
-			LandUseItem aggLandUse = aggregatedAreas.lazyGet(clusterId);
-			IrrigationItem aggIrig = aggregatedIrrigCosts.lazyGet(clusterId);
-			WoodYieldData aggWYield = aggregatedWoodYields.lazyGet(clusterId);
-			CarbonFluxItem aggCFlux = aggregatedCarbonFluxes.lazyGet(clusterId);
-
-			// TODO disabled protected area update. Should probably be done in CountryAgent anyway
-			//landUseItem.updateSuitableArea(rasterInputData.getTimestep().getYear());
-			double suitableAreaThisTime  = landUseItem.getSuitableArea();
-			double suitableAreaSoFar = aggLandUse.getSuitableArea();
-
-			// Irrigation cost
-			if (irrigItem!= null) {
-				aggIrig.setIrrigCost( aggregateMean(aggIrig.getIrrigCost(), suitableAreaSoFar, irrigItem.getIrrigCost(), suitableAreaThisTime));
-				aggIrig.setIrrigConstraint(aggregateMean(aggIrig.getIrrigConstraint(), suitableAreaSoFar, irrigItem.getIrrigConstraint(), suitableAreaThisTime));
-				//LogWriter.println(id + ", " + key + ", " + avgIrigCost.getIrrigCost() + ", from " + areaSoFar + ", " + suitableAreaThisTime + ", " + irrigCost.getIrrigCost());
-			}
-
-			// Aggregate carbon fluxes and wood yields
-			if (carbonFluxItem != null) {
-				for (Map.Entry<LccKey, Double> cfEntry : carbonFluxItem.getConversionCarbonFluxMap().entrySet()) {
-					LccKey lccKey = cfEntry.getKey();
-					double cFluxThisTime = cfEntry.getValue();
-					double cFluxSoFar = aggCFlux.getConversionCarbonFlux(lccKey);
-					double areaThisTime = landUseItem.getLandCoverArea(lccKey.getFromLc());
-					double areaSoFar = aggLandUse.getLandCoverArea(lccKey.getFromLc());
-					double cFluxAgg = aggregateMean(cFluxSoFar, areaSoFar, cFluxThisTime, areaThisTime);
-					aggCFlux.setConversionCarbonFlux(lccKey, cFluxAgg);
-				}
-
-				for (Map.Entry<LandCoverType, Double> cfEntry : carbonFluxItem.getEcosystemCarbonFluxMap().entrySet()) {
-					LandCoverType lcType = cfEntry.getKey();
-					double cFluxThisTime = cfEntry.getValue();
-					double cFluxSoFar = aggCFlux.getEcosystemCarbonFlux(lcType);
-					double areaThisTime = landUseItem.getLandCoverArea(lcType);
-					double areaSoFar = aggLandUse.getLandCoverArea(lcType);
-					double cFluxAgg = aggregateMean(cFluxSoFar, areaSoFar, cFluxThisTime, areaThisTime);
-					aggCFlux.setEcosystemCarbonFlux(lcType, cFluxAgg);
-				}
-			}			
-			/*
-			if (woodYieldItem == null) {
-				LogWriter.println(""+landUseRaster.getYCoordin(key)+" "+landUseRaster.getXCoordin(key));
-			}
-			*/
-			if (woodYieldItem != null) {
-				for (Map.Entry<LccKey, Double> wyEntry : woodYieldItem.getYieldMap().entrySet()) {
-					LccKey lccKey = wyEntry.getKey();
-					double wYieldThisTime = wyEntry.getValue();
-					double wYieldSoFar = aggWYield.getYieldLuc(lccKey);
-					double areaThisTime = landUseItem.getLandCoverArea(lccKey.getFromLc());
-					double areaSoFar = aggLandUse.getLandCoverArea(lccKey.getFromLc());
-					double wYieldAgg = aggregateMean(wYieldSoFar, areaSoFar, wYieldThisTime, areaThisTime);
-					aggWYield.setYieldLuc(lccKey, wYieldAgg);		
-				}
-
-				{
-					// Rotation wood yield
-					double wYieldThisTime = woodYieldItem.getYieldAtRotation();
-					double wYieldSoFar = aggWYield.getYieldRota();
-					double wYieldAgg = aggregateMean(wYieldSoFar, suitableAreaSoFar, wYieldThisTime, suitableAreaThisTime);
-					aggWYield.setYieldRota(wYieldAgg);	
-				}
-
-
-				// Rotation period
-				double forestRotaThisTime = woodYieldItem.getOptimalRotation();
-				double forestRotaSoFar = (aggregatedForestRotation.containsKey(clusterId)) ? aggregatedForestRotation.get(clusterId) : forestRotaThisTime;
-				double forestRotaAgg = aggregateMean(forestRotaSoFar, suitableAreaSoFar, forestRotaThisTime, suitableAreaThisTime);
-				aggregatedForestRotation.put(clusterId, forestRotaAgg);				
-			}
-
-			// Crops yields and area fractions
-			if (yresp != null) {
-				for (CropType crop : CropType.getNonMeatTypes()) {
-					if (!crop.equals(CropType.SETASIDE)) {
-						if (irrigItem!= null) {							
-							double irrigMax = irrigItem.getMaxIrrigAmount(crop);
-
-							if (Double.isNaN(irrigMax))
-								logWarningWithCoord("Can't find irrig max amount for ", key, yieldRaster, crop);
-							else 
-								aggIrig.setMaxIrrigAmount(crop, aggregateMean(aggIrig.getMaxIrrigAmount(crop), suitableAreaSoFar, irrigMax, suitableAreaThisTime));
-						}
-
-						for (YieldType yieldType : YieldType.values()) {
-							double y = yresp.getYield(yieldType, crop);
-							if (Double.isNaN(y))
-								logWarningWithCoord("Problem getting yield for type:" + yieldType + ", ", key, yieldRaster, crop);
-							else {
-								double yieldSoFar = aggYResp.getYield(yieldType, crop);
-								double updatedYield = aggregateMean(yieldSoFar, suitableAreaSoFar, y, suitableAreaThisTime);
-								aggYResp.setYield(yieldType, crop, updatedYield);
-							}
-						}
-
-						double s = yresp.getShockRate(crop);
-						if (!Double.isNaN(s)) {
-							double shockSoFar = aggYResp.getShockRate(crop);
-							double updatedShock = aggregateMean(shockSoFar, suitableAreaSoFar, s, suitableAreaThisTime);
-							aggYResp.setShockRate(crop, updatedShock);
-						}
-					}
-
-					double areaSoFar = aggLandUse.getCropArea(crop);
-					double areaThisTime = landUseItem.getCropArea(crop);
-					double updateCropland = (landUseItem.getLandCoverArea(LandCoverType.CROPLAND) + aggLandUse.getLandCoverArea(LandCoverType.CROPLAND));
-
-					if (updateCropland > 0)
-						aggLandUse.setCropFraction(crop, (areaSoFar + areaThisTime) / updateCropland);
-
-					Intensity intensityThisTime = landUseItem.getIntensity(crop);
-					aggLandUse.setIntensity(crop, intensityThisTime);  // intensity currently is always the same within a location, so can just that any.  If this changes need to average here.
-				}
-			}
-
-			// Land covers areas
-			for (LandCoverType lcType : LandCoverType.values()) {
-				double convAreaThisTime = landUseItem.getConvertibleLandCoverArea(lcType);
-				double convAreaSoFar = aggLandUse.getConvertibleLandCoverArea(lcType);
-				aggLandUse.setConvertibleLandCoverArea(lcType, convAreaSoFar + convAreaThisTime);
-				
-				double protAreaThisTime = landUseItem.getProtectedLandCoverArea(lcType);
-				double protAreaSoFar = aggLandUse.getProtectedLandCoverArea(lcType);
-				aggLandUse.setProtectedLandCoverArea(lcType, protAreaSoFar + protAreaThisTime);
-			}			
-
-
-		}	
-
-		LogWriter.println("Mapping: " + rasterInputData.getCountryInput().getCountry() + ", countFound=" + mappingsFound + ", countMissing=" + mappingsMissing);
-		LogWriter.println("YieldResponsesItem: " + rasterInputData.getCountryInput().getCountry() + ", countFound=" + yRespFound + ", countMissing=" + yRespMissing);
-
-		checkedTotalAreas(landUseRaster, "before");
-		checkedTotalAreas(aggregatedAreas, "after");
-		
-		
-		if (!ModelConfig.IS_FORESTRY_ON) {
-			for (Integer locId : aggregatedYields.keySet()) {
-				aggregatedForestRotation.put(locId, 1.0); // shouldn't matter what this is as long as it's >0
-			}
-		}
-		
-		// TODO shouldn't have missing data in the first place
-		double meanRotation = aggregatedForestRotation.values().stream().mapToDouble(o -> o.doubleValue()).sum() / aggregatedForestRotation.keySet().size();
-		for (Integer locId : aggregatedYields.keySet()) {
-			if (!aggregatedForestRotation.containsKey(locId)) {
-				aggregatedForestRotation.put(locId, meanRotation);
-				LogWriter.printlnWarning("GamsRasterOptimister.convertFromRaster missing timber rotation; substituting average");
-			}
-		}
-
-		return new GamsLocationInput(rasterInputData.getTimestep(), aggregatedYields, aggregatedAreas, aggregatedIrrigCosts, 
-				aggregatedCarbonFluxes, aggregatedWoodYields, rasterInputData.getConversionCosts(), rasterInputData.getCountryInput(), aggregatedForestRotation);
-	}
-
-	private void logWarningWithCoord(String message, RasterKey key, YieldRaster yieldRaster, CropType crop) {
-		logWarningWithCoord(message + "crop:" + crop + ", ", key, yieldRaster);
-	}
-
-	private void logWarningWithCoord(String message, RasterKey key, YieldRaster yieldRaster) {
-		LogWriter.printlnWarning(message + key + ", x:" + yieldRaster.getXCoordin(key) + ", y:" + yieldRaster.getYCoordin(key));
-	}
-
-	private double aggregateMean(double aggV, double aggArea, double newV, double newArea) {
-		if (Double.isNaN(aggV))
-			return newV;
-
-		if (newArea == 0)
-			return aggV;
-
-		return (aggV*aggArea + newV*newArea) / (aggArea + newArea);
-	}
-}
+package ac.ed.lurg.country.gams;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import ac.ed.lurg.ModelConfig;
+import ac.ed.lurg.carbon.CarbonFluxItem;
+import ac.ed.lurg.country.LandCoverChangeItem;
+import ac.ed.lurg.forestry.WoodYieldData;
+import ac.ed.lurg.forestry.WoodYieldItem;
+import ac.ed.lurg.landuse.Intensity;
+import ac.ed.lurg.landuse.IrrigationItem;
+import ac.ed.lurg.landuse.LandUseItem;
+import ac.ed.lurg.landuse.LccKey;
+import ac.ed.lurg.types.CropType;
+import ac.ed.lurg.types.LandCoverType;
+import ac.ed.lurg.types.LandProtectionType;
+import ac.ed.lurg.types.YieldType;
+import ac.ed.lurg.utils.LazyTreeMap;
+import ac.ed.lurg.utils.LogWriter;
+import ac.ed.lurg.yield.YieldRaster;
+import ac.ed.lurg.yield.YieldResponsesItem;
+import ac.sac.raster.IntegerRasterItem;
+import ac.sac.raster.RasterKey;
+import ac.sac.raster.RasterSet;
+
+public class GamsRasterOptimiser {
+	public static final boolean DEBUG = false;
+
+	private GamsRasterInput rasterInputData;
+	private RasterSet<IntegerRasterItem> mapping;
+
+	public GamsRasterOptimiser(GamsRasterInput rasterInputData, RasterSet<IntegerRasterItem> clusterMapping) {
+		this.rasterInputData = rasterInputData;
+		this.mapping = clusterMapping;
+	}
+
+	public GamsRasterOutput run() {
+		// workout similar areas		
+		GamsLocationInput gamsInput = convertFromRaster(rasterInputData);
+
+		long start = System.currentTimeMillis();
+
+		GamsLocationOptimiser opti = new GamsLocationOptimiser(gamsInput);		
+		GamsLocationOutput gamsOutput = opti.run();
+
+		long last = System.currentTimeMillis();
+		LogWriter.println("Took " + (last-start));
+
+		// map results back to raster
+		return convertToRaster(gamsInput, gamsOutput);
+	}
+
+	private GamsRasterOutput convertToRaster(GamsLocationInput gamsInput, GamsLocationOutput gamsOutput) {		
+		RasterSet<LandUseItem> newIntensityRaster = allocAreas(gamsInput.getPreviousLandUse(), gamsOutput, gamsInput.getTimestep().getYear());
+
+		return new GamsRasterOutput(gamsOutput.getStatus(), newIntensityRaster, gamsOutput.getCommoditiesData(),
+				gamsOutput.getNetCarbonFlux(), gamsOutput.getWoodUsageData());
+	}
+
+	private RasterSet<LandUseItem> createWithSameLandCovers(RasterSet<LandUseItem> toCopy) {
+		RasterSet<LandUseItem> theCopy = new RasterSet<LandUseItem>(toCopy.getHeaderDetails()); // TODO why do we make a copy???
+
+		for (Entry<RasterKey, LandUseItem> entry : toCopy.entrySet()) {
+			if (entry.getValue() != null) {
+				LandUseItem newAreasItem =  new LandUseItem(entry.getValue());
+				theCopy.put(entry.getKey(), newAreasItem);
+			}
+		}
+
+		return theCopy;
+	}
+
+	private void checkedTotalAreas(Map<? extends Object, LandUseItem> areaRaster, String comment) {
+		for (LandCoverType l : LandCoverType.values()) {
+			double total = 0;
+			double unprotected = 0;
+			for (LandUseItem a : areaRaster.values()) {
+				if (a != null) {
+					total += a.getLandCoverArea(l, LandProtectionType.CONVERTIBLE) + a.getLandCoverArea(l, LandProtectionType.PROTECTED);
+					unprotected += a.getLandCoverArea(l, LandProtectionType.CONVERTIBLE);
+				}
+										
+			}
+
+			LogWriter.println("Total Area " + comment + ": " + l.getName() + ": total = " + total + ", unprotected = " + unprotected);
+		}
+
+		double suitableArea=0, protectedArea=0;
+		for (LandUseItem a : areaRaster.values()) {
+			if (a != null) {
+				protectedArea += a.getTotalLandCoverArea(LandProtectionType.PROTECTED);
+				suitableArea += a.getSuitableArea();
+			}
+		}
+		LogWriter.println("Total protectedArea " + comment + ": " + protectedArea);
+		LogWriter.println("Total suitableArea " + comment + ": " + suitableArea);
+	}
+
+	private RasterSet<LandUseItem> allocAreas(Map<Integer, ? extends LandUseItem> prevAreasAgg, GamsLocationOutput gamsOutput, int year) {
+		RasterSet<LandUseItem> newLandUseRaster = createWithSameLandCovers(rasterInputData.getPreviousLandUses());
+
+		for (Map.Entry<Integer, LandUseItem> entry : gamsOutput.getLandUses().entrySet()) {
+			Integer locId = entry.getKey();
+			LandUseItem newLandUseAggItem = entry.getValue();
+
+			Set<RasterKey> keys = new HashSet<RasterKey>();
+			for (Entry<RasterKey, IntegerRasterItem> mapEntry : mapping.entrySet()) {
+				IntegerRasterItem iri = mapEntry.getValue();
+
+				if (iri != null && locId == iri.getInt())
+					keys.add(mapEntry.getKey());
+			}
+			
+			RasterSet<LandUseItem> landUseItemsForLocation = newLandUseRaster.createSubsetForKeys(keys);
+			
+			ArrayList<LandCoverChangeItem> landCoverChanges = gamsOutput.getLandCoverChanges().get(locId);
+			
+			if (landCoverChanges != null) {	
+				// Allocate land cover change to grid cells
+				for (LandCoverChangeItem lccItem : landCoverChanges) {
+					LandCoverType fromLC = lccItem.getFromLandCover();
+					LandCoverType toLC = lccItem.getToLandCover();
+					double change = lccItem.getArea();
+					allocAllLandCropsTop(newLandUseRaster, keys, toLC, fromLC, change, locId);
+					
+				}
+			}
+
+			for (RasterKey key : keys) {
+				LandUseItem newLandUseItem = newLandUseRaster.get(key);
+				if (newLandUseItem != null) {
+					for (CropType crop : CropType.values()) {
+						newLandUseItem.setCropFraction(crop, newLandUseAggItem.getCropFraction(crop));
+						newLandUseItem.setIntensity(crop, newLandUseAggItem.getIntensity(crop)); // intensities constant over single aggregated land category
+					}
+				}
+			}
+			
+			if (DEBUG) checkedTotalAreas(landUseItemsForLocation, locId + " after");
+		}
+
+		return newLandUseRaster;
+	}
+
+	private void allocAllLandCropsTop(RasterSet<LandUseItem> newLandUseRaster, Set<RasterKey> keys, LandCoverType toLC, LandCoverType fromLC, double change, Integer locId) {
+		LandCoverType toType = toLC;
+		LandCoverType fromType = fromLC;
+
+		// reverse direction if negative
+		if (change < 0) {
+			change = -change;
+			toType = fromLC;
+			fromType = toLC;
+		}
+
+		double shortfall = allocAllLandCrops(newLandUseRaster, keys, toType, fromType, change);
+
+		if (shortfall > 0.00001) {
+			LogWriter.printlnError("This should never happen, due to GAMS constraint. Not able to incorporate all changes: from " + fromLC + " to " + toLC + " " + locId + ": " + shortfall);
+		}
+	}
+
+	private double allocAllLandCrops(RasterSet<LandUseItem> newLandUseRaster, Set<RasterKey> keys, LandCoverType toType, LandCoverType fromType, double change) {
+		if (DEBUG) LogWriter.println("allocAllLandCrops: from " + fromType + " to " + toType + " change " + change);
+		if (change < 0.00001)
+			return 0;
+
+		double totalShortfall = 0;
+		Set<RasterKey> keysWithSpace = new HashSet<RasterKey>();
+		double totalUnprotectedLC = 0;
+		
+		for (RasterKey key : keys) {
+			LandUseItem newLandUseItem = newLandUseRaster.get(key);
+			totalUnprotectedLC += newLandUseItem.getLandCoverArea(fromType, LandProtectionType.CONVERTIBLE);
+		}
+
+		for (RasterKey key : keys) {
+			//if (DEBUG) LogWriter.println("Processing raster key " + key);
+			LandUseItem newLandUseItem = newLandUseRaster.get(key);
+
+			if (newLandUseItem!=null) {
+				double cellChange = (totalUnprotectedLC > 0) ? change * newLandUseItem.getLandCoverArea(fromType, LandProtectionType.CONVERTIBLE)/totalUnprotectedLC : change / keys.size();
+				double shortfall = newLandUseItem.moveAreas(toType, fromType, cellChange);
+				if (shortfall == 0)
+					keysWithSpace.add(key);
+				else
+					totalShortfall += shortfall;
+				
+				//LogWriter.println(key.toString() + "|" + cellChange);
+			}
+		}
+
+		if (DEBUG) LogWriter.println("allocAllLandCrops: from " + fromType + " to " + toType + " change " + change + " totalShortfall " + totalShortfall);
+		return totalShortfall;
+	}
+
+	@SuppressWarnings("serial")
+	private GamsLocationInput convertFromRaster(GamsRasterInput rasterInputData) {
+		// as a first attempt only going to look at pasture and cereal yields, assuming other yields will be correlated to one or the other.
+		YieldRaster yieldRaster = rasterInputData.getYields();
+		RasterSet<LandUseItem> landUseRaster = rasterInputData.getPreviousLandUses();
+
+		RasterSet<IrrigationItem> irrigRaster = rasterInputData.getIrrigationData();
+		
+		RasterSet<CarbonFluxItem> carbonFluxRaster = rasterInputData.getCarbonFluxes();
+		
+		RasterSet<WoodYieldItem> woodYieldRaster = rasterInputData.getWoodYields();
+
+		// look for inconsistencies
+		for (Entry<RasterKey, YieldResponsesItem> entry : yieldRaster.entrySet()) {
+
+			YieldResponsesItem yresp = entry.getValue();
+			RasterKey key = entry.getKey();
+			CropType crop = CropType.WHEAT;
+
+			if (yresp != null && (yresp.getYield(YieldType.FERT_MAX_IRRIG_MAX, crop) < yresp.getYield(YieldType.FERT_MAX_NO_IRRIG, crop) || yresp.getYield(YieldType.FERT_MAX_IRRIG_MAX, crop) < yresp.getYield(YieldType.NO_FERT_IRRIG_MAX, crop))) {
+				logWarningWithCoord("Inconsistency F only:" + yresp.getYield(YieldType.FERT_MAX_NO_IRRIG, crop) + ", I only" + yresp.getYield(YieldType.NO_FERT_IRRIG_MAX, crop) + ", max " + yresp.getYield(YieldType.FERT_MAX_IRRIG_MAX, crop) + " at ", key, yieldRaster);
+			}
+		}
+
+		// Aggregation
+		LazyTreeMap<Integer, YieldResponsesItem> aggregatedYields = new LazyTreeMap<Integer, YieldResponsesItem>() { 
+			protected YieldResponsesItem createValue() { return new YieldResponsesItem(); }
+		};
+		LazyTreeMap<Integer, LandUseItem> aggregatedAreas = new LazyTreeMap<Integer, LandUseItem>() { 
+			protected LandUseItem createValue() { return new LandUseItem(); }
+		};
+		LazyTreeMap<Integer, IrrigationItem> aggregatedIrrigCosts = new LazyTreeMap<Integer, IrrigationItem>() { 
+			protected IrrigationItem createValue() { return new IrrigationItem(); }
+		};
+		LazyTreeMap<Integer, CarbonFluxItem> aggregatedCarbonFluxes = new LazyTreeMap<Integer, CarbonFluxItem>() { 
+			protected CarbonFluxItem createValue() { return new CarbonFluxItem(); }
+		};
+		LazyTreeMap<Integer, WoodYieldData> aggregatedWoodYields = new LazyTreeMap<Integer, WoodYieldData>() { 
+			protected WoodYieldData createValue() { return new WoodYieldData(); }
+		};		
+		Map<Integer, Double> aggregatedForestRotation = new HashMap<Integer, Double>();
+
+		int yRespFound = 0, yRespMissing = 0;
+		int mappingsFound = 0, mappingsMissing = 0;
+
+		for (RasterKey key : landUseRaster.keySet()) {
+			if (mapping.get(key) == null) {
+				mappingsMissing++;
+				continue;
+			} else {
+				mappingsFound++;
+			}
+			
+			int clusterId = mapping.get(key).getInt();
+
+			YieldResponsesItem yresp = yieldRaster.get(key);
+			if (yresp == null) {
+				yRespMissing++;
+				//logErrorWithCoord("No YieldResponsesItem for ", key, yieldRaster);
+			}
+			else {
+				yRespFound++;
+				//logErrorWithCoord("YieldResponsesItem found for ", key, yieldRaster);
+			}
+			
+			LandUseItem landUseItem  = landUseRaster.get(key);
+			if (landUseItem == null) {
+				LogWriter.printlnError("GamsRasterOptimiser: Can't find landUseItem for " + key);
+				continue;
+			}
+
+			IrrigationItem irrigItem = irrigRaster.get(key);
+			WoodYieldItem woodYieldItem = woodYieldRaster.get(key);
+			CarbonFluxItem carbonFluxItem = carbonFluxRaster.get(key);
+
+			YieldResponsesItem aggYResp = aggregatedYields.lazyGet(clusterId);
+			LandUseItem aggLandUse = aggregatedAreas.lazyGet(clusterId);
+			IrrigationItem aggIrig = aggregatedIrrigCosts.lazyGet(clusterId);
+			WoodYieldData aggWYield = aggregatedWoodYields.lazyGet(clusterId);
+			CarbonFluxItem aggCFlux = aggregatedCarbonFluxes.lazyGet(clusterId);
+
+			double suitableAreaThisTime  = landUseItem.getSuitableArea();
+			double suitableAreaSoFar = aggLandUse.getSuitableArea();
+
+			// Irrigation cost
+			if (irrigItem!= null) {
+				aggIrig.setIrrigCost( aggregateMean(aggIrig.getIrrigCost(), suitableAreaSoFar, irrigItem.getIrrigCost(), suitableAreaThisTime));
+				aggIrig.setIrrigConstraint(aggregateMean(aggIrig.getIrrigConstraint(), suitableAreaSoFar, irrigItem.getIrrigConstraint(), suitableAreaThisTime));
+				//LogWriter.println(id + ", " + key + ", " + avgIrigCost.getIrrigCost() + ", from " + areaSoFar + ", " + suitableAreaThisTime + ", " + irrigCost.getIrrigCost());
+			}
+
+			// Aggregate carbon fluxes and wood yields
+			if (carbonFluxItem != null) {
+				for (Map.Entry<LccKey, Double> cfEntry : carbonFluxItem.getConversionCarbonFluxMap().entrySet()) {
+					LccKey lccKey = cfEntry.getKey();
+					double cFluxThisTime = cfEntry.getValue();
+					double cFluxSoFar = aggCFlux.getConversionCarbonFlux(lccKey);
+					double areaThisTime = landUseItem.getLandCoverArea(lccKey.getFromLc());
+					double areaSoFar = aggLandUse.getLandCoverArea(lccKey.getFromLc());
+					double cFluxAgg = aggregateMean(cFluxSoFar, areaSoFar, cFluxThisTime, areaThisTime);
+					aggCFlux.setConversionCarbonFlux(lccKey, cFluxAgg);
+				}
+
+				for (Map.Entry<LandCoverType, Double> cfEntry : carbonFluxItem.getEcosystemCarbonFluxMap().entrySet()) {
+					LandCoverType lcType = cfEntry.getKey();
+					double cFluxThisTime = cfEntry.getValue();
+					double cFluxSoFar = aggCFlux.getEcosystemCarbonFlux(lcType);
+					double areaThisTime = landUseItem.getLandCoverArea(lcType);
+					double areaSoFar = aggLandUse.getLandCoverArea(lcType);
+					double cFluxAgg = aggregateMean(cFluxSoFar, areaSoFar, cFluxThisTime, areaThisTime);
+					aggCFlux.setEcosystemCarbonFlux(lcType, cFluxAgg);
+				}
+				
+				for (Map.Entry<LandCoverType, Double> cfEntry : carbonFluxItem.getCarbonHorizonFluxMap().entrySet()) {
+					LandCoverType lcType = cfEntry.getKey();
+					double cFluxThisTime = cfEntry.getValue();
+					double cFluxSoFar = aggCFlux.getCarbonHorizonFlux(lcType);
+					double areaThisTime = landUseItem.getLandCoverArea(lcType);
+					double areaSoFar = aggLandUse.getLandCoverArea(lcType);
+					double cFluxAgg = aggregateMean(cFluxSoFar, areaSoFar, cFluxThisTime, areaThisTime);
+					aggCFlux.setCarbonHorizonFlux(lcType, cFluxAgg);
+				}
+			}			
+
+			
+			if (woodYieldItem != null) {
+				for (Map.Entry<LccKey, Double> wyEntry : woodYieldItem.getYieldMap().entrySet()) {
+					LccKey lccKey = wyEntry.getKey();
+					double wYieldThisTime = wyEntry.getValue();
+					double wYieldSoFar = aggWYield.getYieldLuc(lccKey);
+					double areaThisTime = landUseItem.getLandCoverArea(lccKey.getFromLc());
+					double areaSoFar = aggLandUse.getLandCoverArea(lccKey.getFromLc());
+					double wYieldAgg = aggregateMean(wYieldSoFar, areaSoFar, wYieldThisTime, areaThisTime);
+					aggWYield.setYieldLuc(lccKey, wYieldAgg);		
+				}
+
+				{
+					// Rotation wood yield
+					double wYieldThisTime = woodYieldItem.getYieldAtRotation();
+					double wYieldSoFar = aggWYield.getYieldRota();
+					double wYieldAgg = aggregateMean(wYieldSoFar, suitableAreaSoFar, wYieldThisTime, suitableAreaThisTime);
+					aggWYield.setYieldRota(wYieldAgg);	
+				}
+
+				// Rotation period
+				double forestRotaThisTime = woodYieldItem.getOptimalRotation();
+				double forestRotaSoFar = (aggregatedForestRotation.containsKey(clusterId)) ? aggregatedForestRotation.get(clusterId) : forestRotaThisTime;
+				double forestRotaAgg = aggregateMean(forestRotaSoFar, suitableAreaSoFar, forestRotaThisTime, suitableAreaThisTime);
+				aggregatedForestRotation.put(clusterId, forestRotaAgg);				
+			}
+
+			// Crops yields and area fractions
+			if (yresp != null) {
+				for (CropType crop : CropType.getNonMeatTypes()) {
+					if (!crop.equals(CropType.SETASIDE)) {
+						if (irrigItem!= null) {							
+							double irrigMax = irrigItem.getMaxIrrigAmount(crop);
+
+							if (Double.isNaN(irrigMax))
+								logWarningWithCoord("Can't find irrig max amount for ", key, yieldRaster, crop);
+							else 
+								aggIrig.setMaxIrrigAmount(crop, aggregateMean(aggIrig.getMaxIrrigAmount(crop), suitableAreaSoFar, irrigMax, suitableAreaThisTime));
+						}
+
+						for (YieldType yieldType : YieldType.values()) {
+							double y = yresp.getYield(yieldType, crop);
+							if (Double.isNaN(y))
+								logWarningWithCoord("Problem getting yield for type:" + yieldType + ", ", key, yieldRaster, crop);
+							else {
+								double yieldSoFar = aggYResp.getYield(yieldType, crop);
+								double updatedYield = aggregateMean(yieldSoFar, suitableAreaSoFar, y, suitableAreaThisTime);
+								aggYResp.setYield(yieldType, crop, updatedYield);
+							}
+						}
+
+						double s = yresp.getShockRate(crop);
+						if (!Double.isNaN(s)) {
+							double shockSoFar = aggYResp.getShockRate(crop);
+							double updatedShock = aggregateMean(shockSoFar, suitableAreaSoFar, s, suitableAreaThisTime);
+							aggYResp.setShockRate(crop, updatedShock);
+						}
+					}
+
+					double areaSoFar = aggLandUse.getCropArea(crop);
+					double areaThisTime = landUseItem.getCropArea(crop);
+					double updateCropland = (landUseItem.getLandCoverArea(LandCoverType.CROPLAND) + aggLandUse.getLandCoverArea(LandCoverType.CROPLAND));
+
+					if (updateCropland > 0)
+						aggLandUse.setCropFraction(crop, (areaSoFar + areaThisTime) / updateCropland);
+
+					Intensity intensityThisTime = landUseItem.getIntensity(crop);
+					aggLandUse.setIntensity(crop, intensityThisTime);  // intensity currently is always the same within a location, so can just that any.  If this changes need to average here.
+				}
+			}
+
+			// Land covers areas
+			for (LandCoverType lcType : LandCoverType.values()) {
+				double convAreaThisTime = landUseItem.getLandCoverArea(lcType, LandProtectionType.CONVERTIBLE);
+				double convAreaSoFar = aggLandUse.getLandCoverArea(lcType, LandProtectionType.CONVERTIBLE);
+				aggLandUse.setLandCoverArea(lcType, LandProtectionType.CONVERTIBLE, convAreaSoFar + convAreaThisTime);
+
+				double protAreaThisTime = landUseItem.getLandCoverArea(lcType, LandProtectionType.PROTECTED);
+				double protAreaSoFar = aggLandUse.getLandCoverArea(lcType, LandProtectionType.PROTECTED);
+				aggLandUse.setLandCoverArea(lcType, LandProtectionType.PROTECTED, protAreaSoFar + protAreaThisTime);
+			}			
+
+		}	
+
+		LogWriter.println("Mapping: " + rasterInputData.getCountryInput().getCountry() + ", countFound=" + mappingsFound + ", countMissing=" + mappingsMissing);
+		LogWriter.println("YieldResponsesItem: " + rasterInputData.getCountryInput().getCountry() + ", countFound=" + yRespFound + ", countMissing=" + yRespMissing);
+
+		checkedTotalAreas(landUseRaster, "before");
+		checkedTotalAreas(aggregatedAreas, "after");
+		
+		
+		if (!ModelConfig.IS_FORESTRY_ON) {
+			for (Integer locId : aggregatedYields.keySet()) {
+				aggregatedForestRotation.put(locId, 1.0); // shouldn't matter what this is as long as it's >0
+			}
+		}
+		
+		// TODO shouldn't have missing data in the first place
+		double meanRotation = aggregatedForestRotation.values().stream().mapToDouble(o -> o.doubleValue()).sum() / aggregatedForestRotation.keySet().size();
+		for (Integer locId : aggregatedYields.keySet()) {
+			if (!aggregatedForestRotation.containsKey(locId)) {
+				aggregatedForestRotation.put(locId, meanRotation);
+				LogWriter.printlnWarning("GamsRasterOptimister.convertFromRaster missing timber rotation; substituting average");
+			}
+		}
+
+		return new GamsLocationInput(rasterInputData.getTimestep(), aggregatedYields, aggregatedAreas, aggregatedIrrigCosts, 
+				aggregatedCarbonFluxes, aggregatedWoodYields, rasterInputData.getConversionCosts(), rasterInputData.getCountryInput(), aggregatedForestRotation);
+	}
+
+	private void logWarningWithCoord(String message, RasterKey key, YieldRaster yieldRaster, CropType crop) {
+		logWarningWithCoord(message + "crop:" + crop + ", ", key, yieldRaster);
+	}
+
+	private void logWarningWithCoord(String message, RasterKey key, YieldRaster yieldRaster) {
+		LogWriter.printlnWarning(message + key + ", x:" + yieldRaster.getXCoordin(key) + ", y:" + yieldRaster.getYCoordin(key));
+	}
+
+	private double aggregateMean(double aggV, double aggArea, double newV, double newArea) {
+		if (Double.isNaN(aggV))
+			return newV;
+
+		if (newArea == 0)
+			return aggV;
+
+		return (aggV*aggArea + newV*newArea) / (aggArea + newArea);
+	}
+}
diff --git a/src/ac/ed/lurg/forestry/WoodYieldItem.java b/src/ac/ed/lurg/forestry/WoodYieldItem.java
index b8476d4b76f1feb607233903848764d76b679812..9027b28f303c6d95c629f4481c8490e7d737e0c5 100644
--- a/src/ac/ed/lurg/forestry/WoodYieldItem.java
+++ b/src/ac/ed/lurg/forestry/WoodYieldItem.java
@@ -1,111 +1,93 @@
-package ac.ed.lurg.forestry;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import ac.ed.lurg.ModelConfig;
-import ac.ed.lurg.Timestep;
-import ac.ed.lurg.landuse.LandCoverTile;
-import ac.ed.lurg.landuse.LccKey;
-import ac.ed.lurg.types.LandCoverType;
-import ac.ed.lurg.types.LandProtectionType;
-import ac.sac.raster.RasterItem;
-
-public class WoodYieldItem implements RasterItem {
-	public static final int MAX_AGE = 250;
-	
-	private Map<LccKey, Double> yield = new HashMap<LccKey, Double>();
-	private int optimalRotation; // Only applies to TIMBER_FOREST
-	private double yieldAtRotation; // Only applies to TIMBER_FOREST
-	private double currentRotationHarvest; // timber harvested from current rotation
-	
-	
-	public WoodYieldItem() {}
-	
-	public void calcYieldData(Map<LccKey, Double[]> woodYields, Map<LandCoverType, LandCoverTile> landUseTiles, Timestep timestep) {
-		// Mean wood yield for grid cell
-		for (Map.Entry<LccKey, Double[]> entry : woodYields.entrySet()) {
-			LccKey key = entry.getKey();
-			Double[] yields = entry.getValue();
-			
-			LandCoverTile tiles = landUseTiles.get(key.getFromLc());
-			
-			double totalArea = tiles.getTotalArea(LandProtectionType.CONVERTIBLE); // Assuming no harvest from protected areas
-			if (totalArea == 0) {
-				this.yield.put(key, 0.0) ;
-			} else {				
-				double totalYield = 0;
-				for (int age=0; age<=LandCoverTile.getMaxAgeBin(); age++) {
-					int ageCapped = Math.min(age, ModelConfig.CARBON_WOOD_MAX_TIME - 1);
-					totalYield += yields[ageCapped] * tiles.getArea(LandProtectionType.CONVERTIBLE, age);
-				}
-				double meanYield = totalYield / totalArea;
-
-				this.yield.put(key, meanYield);
-			}
-		}
-	}
-	
-	public void calcRotationData(Map<LccKey, Double[]> woodYields, Map<LandCoverType, LandCoverTile> landUseTiles, Timestep timestep, double woodPrice) {
-		// Optimal rotation
-		List<Double> levArr = new ArrayList<Double>();
-		
-		Double[] yields = woodYields.get(new LccKey(LandCoverType.TIMBER_FOREST, LandCoverType.TIMBER_FOREST)); // will need to update if yields no longer symmetrical
-		for (int age = 0; age < ModelConfig.CARBON_WOOD_MAX_TIME; age++) {
-			double yield = yields[age];
-			double lev = (woodPrice * yield * Math.exp(-ModelConfig.DISCOUNT_RATE * age) - ModelConfig.FOREST_ESTABLISHMENT_COST) / (1 - Math.exp(-ModelConfig.DISCOUNT_RATE * age));
-			levArr.add(lev);
-		}
-		
-		double maxLev = Collections.max(levArr);
-		List<Integer> candidates = new ArrayList<Integer>(); // There may be several equal maximum values
-		for (int i = 0; i < levArr.size(); i ++) {
-			if (levArr.get(i) == maxLev) {
-				candidates.add(i);
-			}
-		}
-		
-		optimalRotation = Collections.min(candidates); // Choose shortest rotation		
-		yieldAtRotation = yields[optimalRotation];
-
-		LandCoverTile timberTiles = landUseTiles.get(LandCoverType.TIMBER_FOREST);
-		double timberForestArea = timberTiles.getTotalArea(LandProtectionType.CONVERTIBLE);
-		if (timberForestArea == 0) {
-			currentRotationHarvest = 0.0;
-		} else {
-			currentRotationHarvest += timberTiles.getArea(LandProtectionType.CONVERTIBLE, optimalRotation) * yieldAtRotation;
-		}
-	}
-	
-	public void setDefaultForMissingData() {
-		
-	}
-	
-	public double getYield(LandCoverType fromLc, LandCoverType toLc) {
-		return yield.get(new LccKey(fromLc, toLc));
-	}
-	
-	public double getYield(LccKey key) {
-		return yield.get(key);
-	}
-	
-	public Map<LccKey, Double> getYieldMap() {
-		return yield;
-	}
-
-	public int getOptimalRotation() {
-		return optimalRotation;
-	}
-
-	public double getYieldAtRotation() {
-		return yieldAtRotation;
-	}
-
-	public double getCurrentRotationHarvest() {
-		return currentRotationHarvest;
-	}
-	
-	
-}
+package ac.ed.lurg.forestry;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import ac.ed.lurg.ModelConfig;
+import ac.ed.lurg.Timestep;
+import ac.ed.lurg.landuse.LandCoverTile;
+import ac.ed.lurg.landuse.LccKey;
+import ac.ed.lurg.types.LandCoverType;
+import ac.ed.lurg.types.LandProtectionType;
+import ac.sac.raster.RasterItem;
+
+public class WoodYieldItem implements RasterItem {	
+	private Map<LccKey, Double> yield = new HashMap<LccKey, Double>();
+	private int optimalRotation; // Only applies to TIMBER_FOREST
+	private double yieldAtRotation; // Only applies to TIMBER_FOREST	
+	
+	public WoodYieldItem() {}
+	
+	public void calcYieldData(Map<LccKey, Double[]> woodYields, Map<LandCoverType, LandCoverTile> landUseTiles, Timestep timestep) {
+		// Mean wood yield for grid cell
+		for (Map.Entry<LccKey, Double[]> entry : woodYields.entrySet()) {
+			LccKey key = entry.getKey();
+			Double[] yields = entry.getValue();
+			
+			LandCoverTile tiles = landUseTiles.get(key.getFromLc());
+			
+			double totalArea = tiles.getTotalArea(LandProtectionType.CONVERTIBLE); // Assuming no harvest from protected areas
+			if (totalArea == 0) {
+				this.yield.put(key, 0.0) ;
+			} else {				
+				double totalYield = 0;
+				for (int age : tiles.getAgeKeys()) {
+					int ageCapped = Math.min(age, ModelConfig.CARBON_WOOD_MAX_TIME - 1);
+					totalYield += yields[ageCapped] * tiles.getArea(LandProtectionType.CONVERTIBLE, age);
+				}
+				double meanYield = totalYield / totalArea;
+
+				this.yield.put(key, meanYield);
+			}
+		}
+	}
+	
+	public void calcRotationData(Map<LccKey, Double[]> woodYields, Map<LandCoverType, LandCoverTile> landUseTiles, Timestep timestep, double woodPrice) {
+		// Optimal rotation
+		List<Double> levArr = new ArrayList<Double>();
+		
+		Double[] yields = woodYields.get(new LccKey(LandCoverType.TIMBER_FOREST, LandCoverType.TIMBER_FOREST)); // will need to update if yields no longer symmetrical
+		for (int age = 0; age < ModelConfig.CARBON_WOOD_MAX_TIME; age++) {
+			double yield = yields[age];
+			double lev = (woodPrice * yield * Math.exp(-ModelConfig.DISCOUNT_RATE * age) - ModelConfig.FOREST_ESTABLISHMENT_COST) / (1 - Math.exp(-ModelConfig.DISCOUNT_RATE * age));
+			levArr.add(lev);
+		}
+		
+		double maxLev = Collections.max(levArr);
+		List<Integer> candidates = new ArrayList<Integer>(); // There may be several equal maximum values
+		for (int i = 0; i < levArr.size(); i ++) {
+			if (levArr.get(i) == maxLev) {
+				candidates.add(i);
+			}
+		}
+		
+		optimalRotation = Collections.min(candidates); // Choose shortest rotation		
+		yieldAtRotation = yields[optimalRotation];
+	}
+	
+	public void setDefaultForMissingData() {
+		
+	}
+	
+	public double getYield(LandCoverType fromLc, LandCoverType toLc) {
+		return yield.get(new LccKey(fromLc, toLc));
+	}
+	
+	public double getYield(LccKey key) {
+		return yield.get(key);
+	}
+	
+	public Map<LccKey, Double> getYieldMap() {
+		return yield;
+	}
+
+	public int getOptimalRotation() {
+		return optimalRotation;
+	}
+
+	public double getYieldAtRotation() {
+		return yieldAtRotation;
+	}
+}
diff --git a/src/ac/ed/lurg/landuse/ConversionCostReader.java b/src/ac/ed/lurg/landuse/ConversionCostReader.java
index 0a7a829ba5470b2f524ce0a18c0de93ba8d5cf32..bc87eeaffa2a7b4e5083bcd9385037548de14e14 100644
--- a/src/ac/ed/lurg/landuse/ConversionCostReader.java
+++ b/src/ac/ed/lurg/landuse/ConversionCostReader.java
@@ -1,85 +1,85 @@
-package ac.ed.lurg.landuse;
-
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-
-import ac.ed.lurg.ModelConfig;
-import ac.ed.lurg.types.LandCoverType;
-import ac.ed.lurg.utils.LogWriter;
-
-public class ConversionCostReader {
-	
-	private static final int FROM_COL = 0;
-	private static final int TO_COL = 1;
-	private static final int COST_COL = 2;
-	
-	public Map<LccKey, Double> read() {
-		Map<LccKey, Double> conversionCosts = new HashMap<LccKey, Double>();
-		String filename = ModelConfig.CONVERSION_COST_FILE;
-		try {
-			BufferedReader reader = new BufferedReader(new FileReader(filename)); 
-			String line, toName, fromName;
-			Double cost;
-			reader.readLine(); // read header
-
-			while ((line=reader.readLine()) != null) {
-				String[] tokens = line.split(",");
-				
-				if (tokens.length < 3)
-					LogWriter.printlnError("Too few columns in " + filename + ", " + line);
-				
-				fromName = tokens[FROM_COL];
-				toName = tokens[TO_COL];
-				cost = Double.parseDouble(tokens[COST_COL]);
-				
-				LandCoverType fromLc = LandCoverType.getForName(fromName);
-				LandCoverType toLc = LandCoverType.getForName(toName);
-				
-				conversionCosts.put(new LccKey(fromLc, toLc), cost);
-			} 
-			reader.close(); 
-		
-		} catch (IOException e) {
-			LogWriter.printlnError("Failed in reading conversion costs file");
-			LogWriter.print(e);
-		}
-		LogWriter.println("Processed " + filename);
-		
-		return conversionCosts;
-	}
-	
-	public Map<LccKey, Double> calcFromConfig() {
-		Map<LccKey, Double> conversionCosts = new HashMap<LccKey, Double>();
-		
-		conversionCosts.put(new LccKey(LandCoverType.CROPLAND, LandCoverType.PASTURE), ModelConfig.CROP_DECREASE_COST + ModelConfig.PASTURE_INCREASE_COST);
-		conversionCosts.put(new LccKey(LandCoverType.CROPLAND, LandCoverType.NATURAL), ModelConfig.CROP_DECREASE_COST);
-		conversionCosts.put(new LccKey(LandCoverType.CROPLAND, LandCoverType.TIMBER_FOREST), ModelConfig.CROP_DECREASE_COST + ModelConfig.MANAGED_FOREST_INCREASE_COST);
-		conversionCosts.put(new LccKey(LandCoverType.CROPLAND, LandCoverType.CARBON_FOREST), ModelConfig.CROP_DECREASE_COST + ModelConfig.MANAGED_FOREST_INCREASE_COST);
-		
-		conversionCosts.put(new LccKey(LandCoverType.PASTURE, LandCoverType.CROPLAND), ModelConfig.PASTURE_DECREASE_COST + ModelConfig.CROP_INCREASE_COST);
-		conversionCosts.put(new LccKey(LandCoverType.PASTURE, LandCoverType.NATURAL), ModelConfig.PASTURE_DECREASE_COST);
-		conversionCosts.put(new LccKey(LandCoverType.PASTURE, LandCoverType.TIMBER_FOREST), ModelConfig.PASTURE_DECREASE_COST + ModelConfig.MANAGED_FOREST_INCREASE_COST);
-		conversionCosts.put(new LccKey(LandCoverType.PASTURE, LandCoverType.CARBON_FOREST), ModelConfig.PASTURE_DECREASE_COST + ModelConfig.MANAGED_FOREST_INCREASE_COST);
-		
-		conversionCosts.put(new LccKey(LandCoverType.NATURAL, LandCoverType.CROPLAND), ModelConfig.AGRI_EXPANSION_COST_BASE + ModelConfig.CROP_INCREASE_COST);
-		conversionCosts.put(new LccKey(LandCoverType.NATURAL, LandCoverType.PASTURE), ModelConfig.AGRI_EXPANSION_COST_BASE + ModelConfig.PASTURE_INCREASE_COST);
-		conversionCosts.put(new LccKey(LandCoverType.NATURAL, LandCoverType.TIMBER_FOREST), ModelConfig.MANAGED_FOREST_INCREASE_COST);
-		conversionCosts.put(new LccKey(LandCoverType.NATURAL, LandCoverType.CARBON_FOREST), ModelConfig.MANAGED_FOREST_INCREASE_COST);
-		
-		conversionCosts.put(new LccKey(LandCoverType.TIMBER_FOREST, LandCoverType.CROPLAND), ModelConfig.MANAGED_FOREST_DECREASE_COST + ModelConfig.AGRI_EXPANSION_COST_BASE_MANAGED_FOREST + ModelConfig.CROP_INCREASE_COST);
-		conversionCosts.put(new LccKey(LandCoverType.TIMBER_FOREST, LandCoverType.PASTURE), ModelConfig.MANAGED_FOREST_DECREASE_COST + ModelConfig.AGRI_EXPANSION_COST_BASE_MANAGED_FOREST + ModelConfig.PASTURE_INCREASE_COST);
-		conversionCosts.put(new LccKey(LandCoverType.TIMBER_FOREST, LandCoverType.NATURAL), ModelConfig.MANAGED_FOREST_DECREASE_COST);
-		conversionCosts.put(new LccKey(LandCoverType.TIMBER_FOREST, LandCoverType.CARBON_FOREST), ModelConfig.MANAGED_FOREST_INCREASE_COST + ModelConfig.MANAGED_FOREST_DECREASE_COST);
-		
-		conversionCosts.put(new LccKey(LandCoverType.CARBON_FOREST, LandCoverType.CROPLAND), ModelConfig.MANAGED_FOREST_DECREASE_COST + ModelConfig.AGRI_EXPANSION_COST_BASE_MANAGED_FOREST + ModelConfig.CROP_INCREASE_COST);
-		conversionCosts.put(new LccKey(LandCoverType.CARBON_FOREST, LandCoverType.PASTURE), ModelConfig.MANAGED_FOREST_DECREASE_COST + ModelConfig.AGRI_EXPANSION_COST_BASE_MANAGED_FOREST + ModelConfig.PASTURE_INCREASE_COST);
-		conversionCosts.put(new LccKey(LandCoverType.CARBON_FOREST, LandCoverType.NATURAL), ModelConfig.MANAGED_FOREST_DECREASE_COST);
-		conversionCosts.put(new LccKey(LandCoverType.CARBON_FOREST, LandCoverType.TIMBER_FOREST), ModelConfig.MANAGED_FOREST_INCREASE_COST + ModelConfig.MANAGED_FOREST_DECREASE_COST);
-		
-		return conversionCosts;
-	}
-
-}
+package ac.ed.lurg.landuse;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import ac.ed.lurg.ModelConfig;
+import ac.ed.lurg.types.LandCoverType;
+import ac.ed.lurg.utils.LogWriter;
+
+public class ConversionCostReader {
+	
+	private static final int FROM_COL = 0;
+	private static final int TO_COL = 1;
+	private static final int COST_COL = 2;
+	
+	public Map<LccKey, Double> read() {
+		Map<LccKey, Double> conversionCosts = new HashMap<LccKey, Double>();
+		String filename = ModelConfig.CONVERSION_COST_FILE;
+		try {
+			BufferedReader reader = new BufferedReader(new FileReader(filename)); 
+			String line, toName, fromName;
+			Double cost;
+			reader.readLine(); // read header
+
+			while ((line=reader.readLine()) != null) {
+				String[] tokens = line.split(",");
+				
+				if (tokens.length < 3)
+					LogWriter.printlnError("Too few columns in " + filename + ", " + line);
+				
+				fromName = tokens[FROM_COL];
+				toName = tokens[TO_COL];
+				cost = Double.parseDouble(tokens[COST_COL]);
+				
+				LandCoverType fromLc = LandCoverType.getForName(fromName);
+				LandCoverType toLc = LandCoverType.getForName(toName);
+				
+				conversionCosts.put(new LccKey(fromLc, toLc), cost);
+			} 
+			reader.close(); 
+		
+		} catch (IOException e) {
+			LogWriter.printlnError("Failed in reading conversion costs file");
+			LogWriter.print(e);
+		}
+		LogWriter.println("Processed " + filename);
+		
+		return conversionCosts;
+	}
+	
+	public Map<LccKey, Double> calcFromConfig() {
+		Map<LccKey, Double> conversionCosts = new HashMap<LccKey, Double>();
+		
+		conversionCosts.put(new LccKey(LandCoverType.CROPLAND, LandCoverType.PASTURE), ModelConfig.CROP_DECREASE_COST + ModelConfig.PASTURE_INCREASE_COST);
+		conversionCosts.put(new LccKey(LandCoverType.CROPLAND, LandCoverType.NATURAL), ModelConfig.CROP_DECREASE_COST);
+		conversionCosts.put(new LccKey(LandCoverType.CROPLAND, LandCoverType.TIMBER_FOREST), ModelConfig.CROP_DECREASE_COST + ModelConfig.MANAGED_FOREST_INCREASE_COST);
+		conversionCosts.put(new LccKey(LandCoverType.CROPLAND, LandCoverType.CARBON_FOREST), ModelConfig.CROP_DECREASE_COST + ModelConfig.MANAGED_FOREST_INCREASE_COST);
+		
+		conversionCosts.put(new LccKey(LandCoverType.PASTURE, LandCoverType.CROPLAND), ModelConfig.PASTURE_DECREASE_COST + ModelConfig.CROP_INCREASE_COST);
+		conversionCosts.put(new LccKey(LandCoverType.PASTURE, LandCoverType.NATURAL), ModelConfig.PASTURE_DECREASE_COST);
+		conversionCosts.put(new LccKey(LandCoverType.PASTURE, LandCoverType.TIMBER_FOREST), ModelConfig.PASTURE_DECREASE_COST + ModelConfig.MANAGED_FOREST_INCREASE_COST);
+		conversionCosts.put(new LccKey(LandCoverType.PASTURE, LandCoverType.CARBON_FOREST), ModelConfig.PASTURE_DECREASE_COST + ModelConfig.MANAGED_FOREST_INCREASE_COST);
+		
+		conversionCosts.put(new LccKey(LandCoverType.NATURAL, LandCoverType.CROPLAND), ModelConfig.AGRI_EXPANSION_COST_BASE + ModelConfig.CROP_INCREASE_COST);
+		conversionCosts.put(new LccKey(LandCoverType.NATURAL, LandCoverType.PASTURE), ModelConfig.AGRI_EXPANSION_COST_BASE + ModelConfig.PASTURE_INCREASE_COST);
+		conversionCosts.put(new LccKey(LandCoverType.NATURAL, LandCoverType.TIMBER_FOREST), ModelConfig.MANAGED_FOREST_INCREASE_COST);
+		conversionCosts.put(new LccKey(LandCoverType.NATURAL, LandCoverType.CARBON_FOREST), ModelConfig.MANAGED_FOREST_INCREASE_COST);
+		
+		conversionCosts.put(new LccKey(LandCoverType.TIMBER_FOREST, LandCoverType.CROPLAND), ModelConfig.AGRI_EXPANSION_COST_BASE + ModelConfig.MANAGED_FOREST_DECREASE_COST + ModelConfig.CROP_INCREASE_COST);
+		conversionCosts.put(new LccKey(LandCoverType.TIMBER_FOREST, LandCoverType.PASTURE), ModelConfig.AGRI_EXPANSION_COST_BASE + ModelConfig.MANAGED_FOREST_DECREASE_COST + ModelConfig.PASTURE_INCREASE_COST);
+		conversionCosts.put(new LccKey(LandCoverType.TIMBER_FOREST, LandCoverType.NATURAL), ModelConfig.MANAGED_FOREST_DECREASE_COST);
+		conversionCosts.put(new LccKey(LandCoverType.TIMBER_FOREST, LandCoverType.CARBON_FOREST), ModelConfig.MANAGED_FOREST_INCREASE_COST + ModelConfig.MANAGED_FOREST_DECREASE_COST);
+		
+		conversionCosts.put(new LccKey(LandCoverType.CARBON_FOREST, LandCoverType.CROPLAND), ModelConfig.AGRI_EXPANSION_COST_BASE + ModelConfig.MANAGED_FOREST_DECREASE_COST + ModelConfig.CROP_INCREASE_COST);
+		conversionCosts.put(new LccKey(LandCoverType.CARBON_FOREST, LandCoverType.PASTURE), ModelConfig.AGRI_EXPANSION_COST_BASE + ModelConfig.MANAGED_FOREST_DECREASE_COST + ModelConfig.PASTURE_INCREASE_COST);
+		conversionCosts.put(new LccKey(LandCoverType.CARBON_FOREST, LandCoverType.NATURAL), ModelConfig.MANAGED_FOREST_DECREASE_COST);
+		conversionCosts.put(new LccKey(LandCoverType.CARBON_FOREST, LandCoverType.TIMBER_FOREST), ModelConfig.MANAGED_FOREST_INCREASE_COST + ModelConfig.MANAGED_FOREST_DECREASE_COST);
+		
+		return conversionCosts;
+	}
+
+}
diff --git a/src/ac/ed/lurg/landuse/LandCoverItem.java b/src/ac/ed/lurg/landuse/LandCoverItem.java
index be2eb0b082879a3bdd397cf7094f515ac34b4668..c2733036354277d79e3071038850da8a182ece64 100644
--- a/src/ac/ed/lurg/landuse/LandCoverItem.java
+++ b/src/ac/ed/lurg/landuse/LandCoverItem.java
@@ -1,71 +1,199 @@
-package ac.ed.lurg.landuse;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import ac.ed.lurg.types.LandCoverType;
-import ac.sac.raster.RasterItem;
-
-/** Used to hold less detailed land-cover information
- *  This is used in the initalisation phase, after that land-use is used */
-public class LandCoverItem implements RasterItem {
-	
-	private Map<LandCoverType, Double> landcover = new HashMap<LandCoverType, Double>(); // Land cover fraction
-	private Map<LandCoverType, Map<Integer, Double>> landUseAgeDist = new HashMap<LandCoverType, Map<Integer, Double>>();
-	private double totalArea;
-	private double unavailableFract; // due to slope
-	private double protectedFraction;
-
-	/** Area in Mha */ 
-	public Double getLandCoverArea(LandCoverType landType) {
-		return getLandCoverFract(landType) * totalArea;
-	}
-	
-	public Double getLandCoverFract(LandCoverType landType) {
-		Double d = landcover.get(landType);
-		return d==null ? 0 : d.doubleValue();
-	}
-	
-	public Map<LandCoverType, Double> getLandCoverMap() {
-		return landcover;
-	}
-	
-	/** Area in Mha */ 
-	public double getTotalArea() {
-		return totalArea;
-	}
-
-	/** Area in Mha */ 
-	public void setTotalArea(double totalArea) {
-		this.totalArea = totalArea;
-	}
-	
-	public void setLandCoverFract(LandCoverType landType, double d) {
-		landcover.put(landType, d);
-	}
-	
-	public double getUnavailableFract(){
-		return unavailableFract;
-	}
-	
-	public void setMaxCropFraction(double maxCropFraction){
-		this.unavailableFract=1.0-maxCropFraction;
-	}
-	
-	public void setLandUseAgeDist(LandCoverType lcType, double prop, int ageGroup) {
-		Map<Integer, Double> groupMap = landUseAgeDist.computeIfAbsent(lcType, k -> new HashMap<Integer, Double>());
-		groupMap.put(ageGroup, prop);
-	}
-	
-	public Map<LandCoverType, Map<Integer, Double>> getLandUseAgeDist() {
-		return landUseAgeDist;
-	}
-
-	public double getProtectedFraction() {
-		return protectedFraction;
-	}
-
-	public void setProtectedFraction(double protectedFraction) {
-		this.protectedFraction = protectedFraction;
-	}
+package ac.ed.lurg.landuse;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import ac.ed.lurg.ModelConfig;
+import ac.ed.lurg.types.LandCoverType;
+import ac.ed.lurg.types.LandProtectionType;
+import ac.ed.lurg.utils.LogWriter;
+import ac.sac.raster.RasterItem;
+
+/** Used to hold less detailed land-cover information
+ *  This is used in the initalisation phase, after that land-use is used */
+public class LandCoverItem implements RasterItem {
+	
+	private Map<LandCoverType, Double> landCoverFract = new HashMap<LandCoverType, Double>(); // Land cover fraction
+	private Map<LandCoverType, Map<Integer, Double>> landUseAgeDist = new HashMap<LandCoverType, Map<Integer, Double>>();
+	private Map<LandCoverType, LandCoverTile> landCoverAreas = new HashMap<LandCoverType, LandCoverTile>();
+	private double totalArea;
+	private double unavailableFract; // due to slope
+	private double protectedFraction;
+	private List<LandCoverType> unavailPriorityList = new ArrayList<LandCoverType>(
+			Arrays.asList(LandCoverType.NATURAL, LandCoverType.TIMBER_FOREST, LandCoverType.PASTURE, LandCoverType.CROPLAND));
+
+	/** Area in Mha */ 
+	public Double getLandCoverArea(LandCoverType landType) {
+		return getLandCoverFract(landType) * totalArea;
+	}
+	
+	public Double getLandCoverFract(LandCoverType landType) {
+		Double d = landCoverFract.get(landType);
+		return d==null ? 0 : d.doubleValue();
+	}
+	
+	public Map<LandCoverType, Double> getLandCoverMap() {
+		return landCoverFract;
+	}
+	
+	/** Area in Mha */ 
+	public double getTotalArea() {
+		return totalArea;
+	}
+
+	/** Area in Mha */ 
+	public void setTotalArea(double totalArea) {
+		this.totalArea = totalArea;
+	}
+	
+	public void setLandCoverFract(LandCoverType landType, double d) {
+		landCoverFract.put(landType, d);
+	}
+	
+	public double getUnavailableFract(){
+		return unavailableFract;
+	}
+	
+	public void setMaxCropFraction(double maxCropFraction){
+		this.unavailableFract=1.0-maxCropFraction;
+	}
+	
+	public void setLandUseAgeDist(LandCoverType lcType, double prop, int ageGroup) {
+		Map<Integer, Double> groupMap = landUseAgeDist.computeIfAbsent(lcType, k -> new HashMap<Integer, Double>());
+		groupMap.put(ageGroup, prop);
+	}
+	
+	public Map<LandCoverType, Map<Integer, Double>> getLandUseAgeDist() {
+		return landUseAgeDist;
+	}
+
+	public double getProtectedFraction() {
+		return protectedFraction;
+	}
+
+	public void setProtectedFraction(double protectedFraction) {
+		this.protectedFraction = protectedFraction;
+	}
+	
+	public Map<LandCoverType, LandCoverTile> getLandCoverTiles() {
+		createLandCoverTiles();
+		setUnavailableArea();
+		return landCoverAreas;
+	}
+	
+	public void createLandCoverTiles() {
+		
+		for (LandCoverType lcType : LandCoverType.values()) {
+			landCoverAreas.put(lcType, new LandCoverTile());
+		}
+		
+		double protectableFraction = 0;
+		for (LandCoverType lcType : LandCoverType.getProtectibleTypes()) {
+			protectableFraction += getLandCoverFract(lcType);
+		}
+		// need to scale protectedFraction by protectableFraction and cap at 1.0. Gives fraction of protectable LC types to protect
+		double adjProtectedFraction = Math.min(1.0, protectedFraction / protectableFraction); 
+				
+		for (LandCoverType lcType : LandCoverType.values()) {
+			LandCoverTile tiles = landCoverAreas.get(lcType);
+			double totalArea = getLandCoverArea(lcType);
+			if (totalArea == 0) 
+				continue;
+
+			// static land covers
+			if (!lcType.isConvertible()) { 
+				tiles.addArea(LandProtectionType.UNAVAILABLE, 0, totalArea); // Assumes land cover age is 0
+				continue;
+			}
+			// convertible land covers			
+			double protectedArea;
+			double unprotectedArea;
+			if (lcType.isProtectable()) {
+				protectedArea = totalArea * adjProtectedFraction;
+				unprotectedArea = totalArea * (1 - adjProtectedFraction);
+			} else {
+				protectedArea = 0;
+				unprotectedArea = totalArea;
+			}
+
+			Map<Integer, Double> ageMap = getLandUseAgeDist().get(lcType);
+			double totalProp = ageMap.values().stream().reduce(0.0,  Double::sum);
+			if (totalProp == 0.0) { // If no distribution given then assume land is in oldest age group
+				int maxAgeGroup = ageMap.keySet().stream().max(Integer::compare).get();
+				ageMap.put(maxAgeGroup, 1.0);
+			}
+			if (totalProp > 1.0) {
+				// normalise
+				for (Map.Entry<Integer, Double> entry : ageMap.entrySet()) {
+					ageMap.put(entry.getKey(), entry.getValue() / totalProp);
+				}
+			}
+			
+			// Assuming land cover area all in middle of age group
+			for (Map.Entry<Integer, Double> ageEntry : ageMap.entrySet()) {
+				double prop = ageEntry.getValue();
+				if (prop == 0.0)
+					continue;
+				int ageGroup = ageEntry.getKey();
+				int minAge = (int) (ageGroup - 1) * ModelConfig.LAND_COVER_INIT_AGE_GROUP_SIZE + 1;
+				int midAge = minAge + ModelConfig.LAND_COVER_INIT_AGE_GROUP_SIZE / 2;
+				
+				landCoverAreas.get(lcType).setArea(LandProtectionType.CONVERTIBLE, midAge, unprotectedArea * prop);
+				landCoverAreas.get(lcType).setArea(LandProtectionType.PROTECTED, midAge, protectedArea * prop);				
+			}
+		}
+		
+		// Check that areas have been allocated correctly
+		Map<LandCoverType, Double> initAreas = new HashMap<LandCoverType, Double>();
+		for (LandCoverType lc : LandCoverType.values()) {
+			initAreas.put(lc, getLandCoverArea(lc));
+		}
+		
+		runAreaCheck(initAreas, landCoverAreas);
+	}
+	
+	private void setUnavailableArea() {
+		double alreadyUnavail = landCoverAreas.values().stream().mapToDouble(o -> o.getTotalArea(LandProtectionType.UNAVAILABLE)).sum();
+		double unavailableArea = unavailableFract * totalArea;
+		double shortfall = Math.max(0, unavailableArea - alreadyUnavail);
+		if (shortfall == 0)
+			return;
+
+		for (LandCoverType lcType : unavailPriorityList) {
+			double cArea = landCoverAreas.get(lcType).getTotalArea(LandProtectionType.CONVERTIBLE);
+			double pArea = landCoverAreas.get(lcType).getTotalArea(LandProtectionType.PROTECTED);
+			double totalArea = cArea + pArea;
+			double cProp = cArea / totalArea;
+			
+			double additionalUnavail = Math.min(shortfall, totalArea);
+			if (additionalUnavail > 0) {
+				landCoverAreas.get(lcType).addArea(LandProtectionType.UNAVAILABLE, 0, additionalUnavail);
+				landCoverAreas.get(lcType).removeArea(LandProtectionType.CONVERTIBLE, additionalUnavail * cProp);
+				landCoverAreas.get(lcType).removeArea(LandProtectionType.PROTECTED, additionalUnavail * (1 - cProp));
+				shortfall -= additionalUnavail;
+			}
+			
+			if (shortfall == 0)
+				break;
+		}
+
+		if (shortfall > 0) {
+			LogWriter.printlnWarning("LandUseItem.setUnavailableArea insufficient area to make unavailable");
+		}
+	}
+	
+	public void runAreaCheck(Map<LandCoverType, Double> lcAreas, Map<LandCoverType, LandCoverTile> landCoverAreas) {
+		final double THRESHOLD = 1e-7;
+		for (LandCoverType lcType : landCoverAreas.keySet()) {
+			double initArea = lcAreas.get(lcType);
+			double processedArea = landCoverAreas.get(lcType).getTotalArea();
+			if (Math.abs(initArea - processedArea) > THRESHOLD) {
+				LogWriter.printlnError("LandUseItem: processed area and initial area different: " + lcType.getName() + " " + processedArea + ", " + initArea);
+			}
+		}
+	}
+	
 }
\ No newline at end of file
diff --git a/src/ac/ed/lurg/landuse/LandCoverTile.java b/src/ac/ed/lurg/landuse/LandCoverTile.java
index ecc8d4c077752ad978be8aa4eb87563be8fcf098..776add086d3443d9de4ec61c5b3cc2f54cb2d249 100644
--- a/src/ac/ed/lurg/landuse/LandCoverTile.java
+++ b/src/ac/ed/lurg/landuse/LandCoverTile.java
@@ -1,113 +1,187 @@
-package ac.ed.lurg.landuse;
-
-import java.io.Serializable;
-import java.util.HashMap;
-import java.util.Map;
-
-import ac.ed.lurg.ModelConfig;
-import ac.ed.lurg.utils.LogWriter;
-import ac.sac.raster.InterpolatingRasterItem;
-import ac.ed.lurg.types.LandProtectionType;
-
-public class LandCoverTile implements Serializable, InterpolatingRasterItem<LandCoverTile> {
-	private static final long serialVersionUID = 3208516181615267472L;
-	private static final int MAX_AGE = 250;
-	
-	private Map<LandProtectionType, LandParcel> areas;
-	
-	public LandCoverTile() {
-		this.areas = new HashMap<LandProtectionType, LandParcel>();
-		for (LandProtectionType lpType : LandProtectionType.values()) {
-			areas.put(lpType, new LandParcel());
-		}
-	}
-
-	public double getArea(LandProtectionType lpType, int age) {
-		return areas.get(lpType).get(age);
-	}
-	
-	public void addArea(LandProtectionType lpType, double a) {
-		areas.get(lpType).add(0, a); // new area is always age=0
-	}
-	
-	public void addArea(LandProtectionType lpType, int age, double a) {
-		areas.get(lpType).add(age, a);
-	}
-	
-	public void setArea(LandProtectionType lpType, int age, double a) {
-		areas.get(lpType).set(age, a);
-	}
-	
-	public double getTotalArea(LandProtectionType lpType) {
-		return areas.get(lpType).getTotal();
-	}
-	
-	public void removeArea(LandProtectionType lpType, double a) { // Subtracts area uniformly
-		areas.get(lpType).subtractUniform(a);
-	}
-	
-	public void removeArea(LandProtectionType lpType, int age, double a) {
-		areas.get(lpType).subtract(age, a);
-	}
-
-	public double getTotalArea() {
-		double area = 0;
-		for (LandProtectionType lpType : LandProtectionType.values()) {
-			area += getTotalArea(lpType);
-		}
-		return area;
-	}
-
-	public void resetAreaForAge(LandProtectionType lpType, int age) {
-		areas.get(lpType).resetAge(age);
-	}
-	
-	public void resetAreaForAge(LandProtectionType lpType, int age, double area) {
-		if (area > getArea(lpType, age)) {
-			LogWriter.printlnError("LandCoverTile.resetAreaForAge: not enough area");
-			return;
-		}
-		addArea(lpType, area);
-		removeArea(lpType, age, area);
-	}
-	
-	public void removeAllArea() {
-		for (LandProtectionType lpType : LandProtectionType.values()) {
-			areas.get(lpType).clearAll();
-		}
-	}
-	
-	public void incrementAge() {
-		for (int t = 0; t < ModelConfig.TIMESTEP_SIZE; t++) {
-			for (LandProtectionType lpType : LandProtectionType.values()) {
-				areas.get(lpType).incrementAge();
-			}
-		}
-	}
-
-	// Moves area from convertible to protected, maintaining age classes
-	public void moveAreaToProtected(double area) {
-		double convertibleA = getTotalArea(LandProtectionType.CONVERTIBLE);
-		if (area > convertibleA) {
-			LogWriter.printlnWarning("LandCoverTile.moveAreaToProtected cannot protect all required area");
-			area = Math.min(convertibleA, area);
-		}
-		double fractToMove = area / convertibleA;
-		for (int i = 0; i <= MAX_AGE; i ++) {
-			double areaToMove = getArea(LandProtectionType.CONVERTIBLE, i) * fractToMove;
-			addArea(LandProtectionType.PROTECTED, i, areaToMove);
-			removeArea(LandProtectionType.CONVERTIBLE, i, areaToMove);
-		}
-	}
-	
-	public static int getMaxAgeBin() {
-		return MAX_AGE;
-	}
-
-	@Override
-	public void interpolateAll(LandCoverTile fromItem, LandCoverTile toItem, double factor) {
-		// TODO Auto-generated method stub
-		
-	}
-
-}
+package ac.ed.lurg.landuse;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import ac.ed.lurg.ModelConfig;
+import ac.ed.lurg.utils.LogWriter;
+import ac.sac.raster.InterpolatingRasterItem;
+import ac.ed.lurg.types.LandProtectionType;
+
+public class LandCoverTile implements Serializable, InterpolatingRasterItem<LandCoverTile> {
+	private static final long serialVersionUID = 3208516181615267472L;
+	
+	private Map<LandKey, Double> areas = new HashMap<LandKey, Double>();
+	
+	public LandCoverTile() {}
+
+	public double getArea(LandProtectionType lpType, int age) {
+		LandKey key = new LandKey(lpType, age);
+		return getArea(key);
+	}
+	
+	public double getArea(LandKey key) {
+		return areas.getOrDefault(key, 0.0);
+	}
+	
+	public void setArea(LandProtectionType lpType, int age, double a) {
+		LandKey key = new LandKey(lpType, age);
+		setArea(key, a);
+	}
+	
+	public void setArea(LandKey key, double a) {
+		areas.put(key, a);
+	}
+	
+	public void addArea(LandProtectionType lpType, int age, double a) {
+		LandKey key = new LandKey(lpType, age);
+		double prevArea = getArea(key);
+		areas.put(key, prevArea + a);
+	}
+	
+	public void addArea(LandProtectionType lpType, double a) {
+		double totalArea = getTotalArea(lpType);
+		if (totalArea == 0) {
+			for (int age = 0; age <= 150; age+=10) {
+				addArea(lpType, age, a/16); // assuming even distribution
+			}
+			
+		} else {
+			double factor = (a + totalArea) / totalArea;
+			for (LandKey key : areas.keySet()) {
+				if (key.getLpType().equals(lpType)) {
+					double prevArea = getArea(key);
+					setArea(key, prevArea * factor);
+				}
+			}
+		}
+	}
+
+	public double getTotalArea(LandProtectionType lpType) {
+		double a = areas.keySet()
+				.stream()
+				.filter(k -> k.getLpType().equals(lpType))
+				.mapToDouble(k -> areas.get(k))
+				.sum();
+		return a;
+	}
+	
+	public void removeArea(LandProtectionType lpType, int age, double a) {
+		if (a == 0)
+			return;
+		LandKey key = new LandKey(lpType, age);
+		double prevArea = getArea(key);
+		if (a > prevArea) {
+			LogWriter.printlnError("LandCoverTile: attempting to remove too much area.");
+			a = Math.min(a, prevArea);
+		}
+		setArea(key, prevArea - a);
+	}
+
+	public void removeArea(LandProtectionType lpType, double a) { // Subtracts area uniformly
+		if (a == 0)
+			return;
+		double totalArea = getTotalArea(lpType);
+		double areaToRemove = Math.min(a, totalArea);
+		if (Math.abs(a - areaToRemove) > 1e-10) {
+			LogWriter.printlnError("LandCoverTile: attempting to remove too much area.");
+		}
+		double factor = (totalArea - areaToRemove) / totalArea;
+		for (LandKey key : areas.keySet()) {
+			if (key.getLpType().equals(lpType)) {
+				double prevArea = getArea(key);
+				setArea(key, prevArea * factor);
+			}
+		}
+	}
+	
+	public double getTotalArea() {
+		double area = 0;
+		for (LandProtectionType lpType : LandProtectionType.values()) {
+			area += getTotalArea(lpType);
+		}
+		return area;
+	}
+
+	public void resetAreaForAge(LandProtectionType lpType, int age) {
+		LandKey key = new LandKey(lpType, age);
+		double a = getArea(key);
+		areas.remove(key);
+		setArea(lpType, 0, a);
+	}
+	
+	public void resetAreaForAge(LandProtectionType lpType, int age, double a) {
+		a = Math.min(getArea(lpType, age), a);
+		removeArea(lpType, age, a);
+		addArea(lpType, 0, a);
+	}
+	
+	public void removeAllArea(LandProtectionType lpType) {
+		Map<LandKey, Double> newAreas = new HashMap<LandKey, Double>();
+		for (LandKey key : areas.keySet()) {
+			if (!key.getLpType().equals(lpType)) {
+				newAreas.put(key, getArea(key));;
+			}
+		}
+		areas.clear();
+		areas.putAll(newAreas);
+	}
+	
+	public void incrementAge() {
+		Map<LandKey, Double> newAreas = new HashMap<LandKey, Double>();
+		for (LandKey key : areas.keySet()) {
+			LandKey newKey = new LandKey(key.getLpType(), key.getAge() + ModelConfig.TIMESTEP_SIZE);
+			newAreas.put(newKey, getArea(key));
+		}
+		areas.clear();
+		areas.putAll(newAreas);
+	}
+
+	// Moves area between land protection types
+	public void moveArea(LandProtectionType fromPt, LandProtectionType toPt, double area) {
+		double fromA = getTotalArea(fromPt);
+		if (area > fromA) {
+			LogWriter.printlnWarning("LandCoverTile.moveAreaToProtected cannot protect all required area");
+			area = Math.min(fromA, area);
+		}
+		double fractToMove = area / fromA;
+		for (LandKey key : areas.keySet()) {
+			if (key.getLpType().equals(fromPt)) {
+				double areaToMove = getArea(key) * fractToMove;
+				addArea(toPt, key.getAge(), areaToMove);
+				removeArea(fromPt, key.getAge(), areaToMove);
+			}
+		}
+	}
+	
+	public Set<Integer> getAgeKeys() { // TODO can be more efficient
+		Set<Integer> ageKeys = new HashSet<Integer>();
+		for (LandKey key : areas.keySet()) {
+			ageKeys.add(key.getAge());
+		}
+		return ageKeys;
+	}
+	
+	// remove 0 areas
+	public void cleanUp() {
+		Map<LandKey, Double> newAreas = new HashMap<LandKey, Double>();
+		for (LandKey key : areas.keySet()) {
+			double a = getArea(key);
+			if (a > 0) {
+				newAreas.put(key, a);
+			}
+		}
+		areas.clear();
+		areas.putAll(newAreas);
+	}
+
+	@Override
+	public void interpolateAll(LandCoverTile fromItem, LandCoverTile toItem, double factor) {
+		// TODO Auto-generated method stub
+		
+	}
+
+}
diff --git a/src/ac/ed/lurg/landuse/LandKey.java b/src/ac/ed/lurg/landuse/LandKey.java
new file mode 100644
index 0000000000000000000000000000000000000000..aae009aea20747fc517b8101cbe78c97f5cafa7d
--- /dev/null
+++ b/src/ac/ed/lurg/landuse/LandKey.java
@@ -0,0 +1,45 @@
+package ac.ed.lurg.landuse;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+import ac.ed.lurg.types.LandProtectionType;
+
+public class LandKey implements Serializable {
+	private static final long serialVersionUID = -1173322425708587699L;
+	
+	private LandProtectionType lpType;
+	private int age; // years since conversion
+	
+	public LandKey(LandProtectionType lpType, int age) {
+		super();
+		this.lpType = lpType;
+		this.age = age;
+	}
+	
+	public LandProtectionType getLpType() {
+		return lpType;
+	}
+
+	public int getAge() {
+		return age;
+	}
+
+	@Override
+	public int hashCode() {
+		return Objects.hash(age, lpType);
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		LandKey other = (LandKey) obj;
+		return age == other.age && lpType == other.lpType;
+	}
+	
+}
diff --git a/src/ac/ed/lurg/landuse/LandParcel.java b/src/ac/ed/lurg/landuse/LandParcel.java
deleted file mode 100644
index 7dc3aa643328e5ddd8808f64ab2b495c5c7851c2..0000000000000000000000000000000000000000
--- a/src/ac/ed/lurg/landuse/LandParcel.java
+++ /dev/null
@@ -1,89 +0,0 @@
-package ac.ed.lurg.landuse;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-
-import ac.ed.lurg.utils.LogWriter;
-
-public class LandParcel implements Serializable {
-	private static final long serialVersionUID = -114360206977984739L;
-	Map<Integer, Double> parcels;
-	
-	public LandParcel() {
-		this.parcels = new TreeMap<Integer, Double>();
-	}
-	
-	public void set(int age, double area) {
-		parcels.put(age, area);
-	}
-	
-	public double get(int age) {
-		return parcels.getOrDefault(age, 0.0);
-	}
-	
-	public void add(int age, double area) {
-		double newArea = get(age) + area;
-		set(age, newArea);
-	}
-	
-	public void subtract(int age, double area) {
-		double prevArea = get(age);
-		double newArea;
-		if (prevArea < area) {
-			LogWriter.printlnError("LandParcel: Attempting to subtract too much area");
-			newArea = 0.0;
-		} else {
-			newArea = prevArea - area;
-		}
-		set(age, newArea);
-	}
-	
-	public void subtractUniform(double area) {
-		if (area <= 0)
-			return;
-		double totalArea = getTotal();
-		double areaToRemove = Math.min(totalArea, area);
-		if (Math.abs(area - areaToRemove) > 1e-10) {
-			LogWriter.printlnError("LandParcel.subtractUniform: attempting to remove too much area: " + area + ", available: " + totalArea);
-		}
-		double factor = (totalArea - areaToRemove) / totalArea;
-		for (int i : parcels.keySet()) {
-			double newArea = get(i) * factor;
-			set(i, newArea);
-		}
-	}
-	
-	public double getTotal() {
-		return parcels.values().stream().reduce(0.0, Double::sum);
-	}
-	
-	public void resetAge(int age) {
-		double area = get(age);
-		set(age, 0.0);
-		set(0, area);
-	}
-	
-	public void incrementAge() {
-		if (parcels.size() == 0)
-			return;
-		
-		Set<Integer> keys = parcels.keySet();
-		List<Integer> orderedKeys = new ArrayList<Integer>(keys);
-		Collections.sort(orderedKeys);
-		Collections.reverse(orderedKeys);
-		for (int age : orderedKeys) {
-			double area = get(age);
-			set(age + 1, area);
-			parcels.remove(age);
-		}
-	}	
-	
-	public void clearAll() {
-		parcels.clear();
-	}
-}
diff --git a/src/ac/ed/lurg/landuse/LandTileReader.java b/src/ac/ed/lurg/landuse/LandTileReader.java
index d36ff6911dd547ae9256b3e743eeb18e5d240344..4a3b3aae412bd56ee8917d5aedb41a622419026a 100644
--- a/src/ac/ed/lurg/landuse/LandTileReader.java
+++ b/src/ac/ed/lurg/landuse/LandTileReader.java
@@ -1,26 +1,26 @@
-package ac.ed.lurg.landuse;
-
-import java.util.Map;
-
-import ac.ed.lurg.types.LandCoverType;
-import ac.sac.raster.AbstractTabularRasterReader;
-import ac.sac.raster.RasterKey;
-import ac.sac.raster.RasterSet;
-
-public class LandTileReader extends AbstractTabularRasterReader<LandCoverItem> {
-	private static final int MIN_COLS = 6;
-	
-	public LandTileReader(RasterSet<LandCoverItem> data) {
-		super("[ |\t]+", MIN_COLS, data);
-	}
-
-	@Override
-	protected void setData(RasterKey key, LandCoverItem item, Map<String, Double> rowValues) {
-		int ageGroup = (int) getValueForCol(rowValues, "AgeGroup");
-		item.setLandUseAgeDist(LandCoverType.CROPLAND, 	    getValueForCol(rowValues, "CROPLAND"), 	    ageGroup);
-		item.setLandUseAgeDist(LandCoverType.PASTURE, 		getValueForCol(rowValues, "PASTURE"),  	    ageGroup);
-		item.setLandUseAgeDist(LandCoverType.TIMBER_FOREST, getValueForCol(rowValues, "FOREST"), 		ageGroup);
-		item.setLandUseAgeDist(LandCoverType.CARBON_FOREST, getValueForCol(rowValues, "FOREST"), 		ageGroup);
-		item.setLandUseAgeDist(LandCoverType.NATURAL, 		getValueForCol(rowValues, "FOREST"), 		ageGroup);
-	}
-}
+package ac.ed.lurg.landuse;
+
+import java.util.Map;
+
+import ac.ed.lurg.types.LandCoverType;
+import ac.sac.raster.AbstractTabularRasterReader;
+import ac.sac.raster.RasterKey;
+import ac.sac.raster.RasterSet;
+
+public class LandTileReader extends AbstractTabularRasterReader<LandCoverItem> {
+	private static final int MIN_COLS = 6;
+	
+	public LandTileReader(RasterSet<LandCoverItem> data) {
+		super("[ |\t]+", MIN_COLS, data);
+	}
+
+	@Override
+	protected void setData(RasterKey key, LandCoverItem item, Map<String, Double> rowValues) {
+		int ageGroup = (int) getValueForCol(rowValues, "AgeGroup");
+		item.setLandUseAgeDist(LandCoverType.CROPLAND, 	    getValueForCol(rowValues, "CROPLAND"), 	    ageGroup);
+		item.setLandUseAgeDist(LandCoverType.PASTURE, 		getValueForCol(rowValues, "PASTURE"),  	    ageGroup);
+		item.setLandUseAgeDist(LandCoverType.TIMBER_FOREST, getValueForCol(rowValues, "FOREST"), 		ageGroup);
+		item.setLandUseAgeDist(LandCoverType.CARBON_FOREST, getValueForCol(rowValues, "FOREST"), 		ageGroup);
+		item.setLandUseAgeDist(LandCoverType.NATURAL, 		getValueForCol(rowValues, "FOREST"), 		ageGroup);
+	}
+}
diff --git a/src/ac/ed/lurg/landuse/LandUseBinarySerializer.java b/src/ac/ed/lurg/landuse/LandUseBinarySerializer.java
index 832afbc4be74b15c8bef0327c4a3317d16c28e4f..320441b88226bf90cb3834985ba396998e5a4072 100644
--- a/src/ac/ed/lurg/landuse/LandUseBinarySerializer.java
+++ b/src/ac/ed/lurg/landuse/LandUseBinarySerializer.java
@@ -1,194 +1,186 @@
-package ac.ed.lurg.landuse;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-
-import ac.ed.lurg.ModelConfig;
-import ac.ed.lurg.types.LandCoverType;
-import ac.ed.lurg.types.LandProtectionType;
-import ac.ed.lurg.utils.LogWriter;
-import ac.sac.raster.RasterKey;
-import ac.sac.raster.RasterSet;
-
-public class LandUseBinarySerializer {
-	private static final int NUM_PROTECTION_CLASSES = 3; // convertible, protected, unavailable
-	private static final int NUM_AGE_CLASSES = LandCoverTile.getMaxAgeBin() + 1;
-	private static final int CONVERTIBLE_IDX = 0;
-	private static final int PROTECTED_IDX = 1;
-	private static final int UNVAVAILABLE_IDX = 2;
-
-	public LandUseBinarySerializer() {}
-
-	public void serializeLandUse(RasterSet<LandUseItem> luRaster) {
-		long startTime = System.currentTimeMillis();
-
-		FileOutputStream outputStream = null;
-		DataOutputStream writer = null;
-
-		try {
-			outputStream = new FileOutputStream(ModelConfig.SERIALIZED_LAND_COVER_FILE);
-			writer = new DataOutputStream(new BufferedOutputStream(outputStream));
-
-			// TODO file header and checks: num lc types, order, num protection classes, vector size
-			int count =0;
-			for (Map.Entry<RasterKey, LandUseItem> luEntry : luRaster.entrySet()) {
-				count++;
-				RasterKey key = luEntry.getKey();
-				LandUseItem luItem = luEntry.getValue();
-
-				// Write coordinates
-				writer.writeDouble(key.getCol());
-				writer.writeDouble(key.getRow());
-
-				Map<LandCoverType, LandCoverTile> tiles = luItem.getLandCoverTiles();
-
-				// Convertible
-				for (LandCoverType lcType : LandCoverType.values()) {
-					LandCoverTile tile = tiles.get(lcType);
-					for (int a = 0; a < NUM_AGE_CLASSES; a++) {
-						writer.writeDouble(tile.getArea(LandProtectionType.CONVERTIBLE, a));
-					}
-				}
-
-				// Protected
-				for (LandCoverType lcType : LandCoverType.values()) {
-					LandCoverTile tile = tiles.get(lcType);
-					for (int a = 0; a < NUM_AGE_CLASSES; a++) {
-						writer.writeDouble(tile.getArea(LandProtectionType.PROTECTED, a));
-					}
-				}
-
-				// Unavailable
-				for (LandCoverType lcType : LandCoverType.values()) {
-					LandCoverTile tile = tiles.get(lcType);
-					writer.writeDouble(tile.getTotalArea(LandProtectionType.UNAVAILABLE));
-				}
-				//LogWriter.println(""+count);
-			}
-			writer.close();
-
-
-		}
-		catch (Exception e) {
-			LogWriter.printlnError("Problem reading data file " + ModelConfig.SERIALIZED_LAND_COVER_FILE);
-			LogWriter.print(e);
-			throw new RuntimeException(e);
-		}
-		finally {
-			if (outputStream != null) {
-				try {
-					outputStream.close();
-				} catch (IOException e) {
-					LogWriter.print(e);
-				}
-			}
-			if (writer != null) {
-				try {
-					writer.close();
-				} catch (IOException e) {
-					LogWriter.print(e);
-				}
-			}
-		}
-		LogWriter.println("Serialized land cover " + ModelConfig.SERIALIZED_LAND_COVER_FILE + ", took " + (System.currentTimeMillis() - startTime) + " ms");
-	}
-
-	public void deserializeLandUse(RasterSet<LandUseItem> luRaster) {
-		long startTime = System.currentTimeMillis();
-
-		FileInputStream inputStream = null;
-		DataInputStream reader = null;
-
-		try {
-			inputStream = new FileInputStream(ModelConfig.SERIALIZED_LAND_COVER_FILE);
-			BufferedInputStream buffInputStream = new BufferedInputStream(inputStream);
-			buffInputStream.mark(8);
-			reader = new DataInputStream(buffInputStream);
-			
-			int count=0;
-
-			while(reader.available() > 0) {
-				// Coordinates loop
-
-				int col = (int) reader.readDouble();
-				int row = (int) reader.readDouble();
-
-				Map<LandCoverType, LandCoverTile> landCoverAreas = new HashMap<LandCoverType, LandCoverTile>();
-				for (LandCoverType lcType: LandCoverType.values()) {
-					landCoverAreas.put(lcType, new LandCoverTile());
-				}
-
-				LandUseItem luItem = luRaster.get(new RasterKey(col, row));
-
-				// Protection class loop
-				for (int protIdx = 0; protIdx < NUM_PROTECTION_CLASSES; protIdx++) {
-
-					switch(protIdx) {
-					case CONVERTIBLE_IDX:
-						for (LandCoverType lcType : LandCoverType.values()) {
-							for (int age = 0; age < NUM_AGE_CLASSES; age++) {
-								double area = reader.readDouble();
-								if (area > 0)
-									landCoverAreas.get(lcType).setArea(LandProtectionType.CONVERTIBLE, age, area);
-							}
-						}
-						break;
-					case PROTECTED_IDX:
-						for (LandCoverType lcType : LandCoverType.values()) {
-							for (int age = 0; age < NUM_AGE_CLASSES; age++) {
-								double area = reader.readDouble();
-								if (area > 0)
-									landCoverAreas.get(lcType).setArea(LandProtectionType.PROTECTED, age, area);
-							}
-						}	
-						break;
-					case UNVAVAILABLE_IDX:
-						for (LandCoverType lcType : LandCoverType.values()) {
-							double area = reader.readDouble();
-							if (area > 0)
-								landCoverAreas.get(lcType).setArea(LandProtectionType.UNAVAILABLE, 0, area); // TODO Do we need to keep track of age?
-						}
-					}
-				}
-
-				luItem.overwriteLandCoverAreas(landCoverAreas);
-				count++;
-				/*if(Math.floorMod(count, 1000)==0) {
-					LogWriter.println(""+count);
-				}*/
-			}
-
-			reader.close();
-		}
-		catch (Exception e) {
-			LogWriter.printlnError("Problem reading data file " + ModelConfig.SERIALIZED_LAND_COVER_FILE);
-			LogWriter.print(e);
-			throw new RuntimeException(e);
-		}
-		finally {
-			if (inputStream != null) {
-				try {
-					inputStream.close();
-				} catch (IOException e) {
-					LogWriter.print(e);
-				}
-			}
-			if (reader != null) {
-				try {
-					reader.close();
-				} catch (IOException e) {
-					LogWriter.print(e);
-				}
-			}
-		}
-		LogWriter.println("Deserialized land cover " + ModelConfig.SERIALIZED_LAND_COVER_FILE + ", took " + (System.currentTimeMillis() - startTime) + " ms");
-	}
-
-}
+package ac.ed.lurg.landuse;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import ac.ed.lurg.ModelConfig;
+import ac.ed.lurg.types.LandCoverType;
+import ac.ed.lurg.types.LandProtectionType;
+import ac.ed.lurg.utils.LogWriter;
+import ac.sac.raster.RasterKey;
+import ac.sac.raster.RasterSet;
+
+public class LandUseBinarySerializer {
+	private static final int NUM_PROTECTION_CLASSES = 3; // convertible, protected, unavailable
+//	private static final int NUM_AGE_CLASSES = LandCoverTile.getMaxAgeBin() + 1;
+	private static final int NUM_AGE_CLASSES = 250 + 1;
+	private static final int CONVERTIBLE_IDX = 0;
+	private static final int PROTECTED_IDX = 1;
+	private static final int UNVAVAILABLE_IDX = 2;
+
+	public LandUseBinarySerializer() {}
+
+	public void serializeLandUse(RasterSet<LandUseItem> luRaster) {
+		long startTime = System.currentTimeMillis();
+
+		FileOutputStream outputStream = null;
+		DataOutputStream writer = null;
+
+		try {
+			outputStream = new FileOutputStream(ModelConfig.SERIALIZED_LAND_COVER_FILE);
+			writer = new DataOutputStream(new BufferedOutputStream(outputStream));
+
+			// TODO file header and checks: num lc types, order, num protection classes, vector size
+			for (Map.Entry<RasterKey, LandUseItem> luEntry : luRaster.entrySet()) {
+				RasterKey key = luEntry.getKey();
+				LandUseItem luItem = luEntry.getValue();
+
+				// Write coordinates
+				writer.writeDouble(key.getCol());
+				writer.writeDouble(key.getRow());
+
+				Map<LandCoverType, LandCoverTile> tiles = luItem.getLandCoverTiles();
+
+				// Convertible
+				for (LandCoverType lcType : LandCoverType.values()) {
+					LandCoverTile tile = tiles.get(lcType);
+					for (int a = 0; a < NUM_AGE_CLASSES; a++) {
+						writer.writeDouble(tile.getArea(LandProtectionType.CONVERTIBLE, a));
+					}
+				}
+
+				// Protected
+				for (LandCoverType lcType : LandCoverType.values()) {
+					LandCoverTile tile = tiles.get(lcType);
+					for (int a = 0; a < NUM_AGE_CLASSES; a++) {
+						writer.writeDouble(tile.getArea(LandProtectionType.PROTECTED, a));
+					}
+				}
+
+				// Unavailable
+				for (LandCoverType lcType : LandCoverType.values()) {
+					LandCoverTile tile = tiles.get(lcType);
+					writer.writeDouble(tile.getTotalArea(LandProtectionType.UNAVAILABLE));
+				}
+			}
+			writer.close();
+
+
+		}
+		catch (Exception e) {
+			LogWriter.printlnError("Problem reading data file " + ModelConfig.SERIALIZED_LAND_COVER_FILE);
+			LogWriter.print(e);
+			throw new RuntimeException(e);
+		}
+		finally {
+			if (outputStream != null) {
+				try {
+					outputStream.close();
+				} catch (IOException e) {
+					LogWriter.print(e);
+				}
+			}
+			if (writer != null) {
+				try {
+					writer.close();
+				} catch (IOException e) {
+					LogWriter.print(e);
+				}
+			}
+		}
+		LogWriter.println("Serialized land cover " + ModelConfig.SERIALIZED_LAND_COVER_FILE + ", took " + (System.currentTimeMillis() - startTime) + " ms");
+	}
+
+	public void deserializeLandUse(RasterSet<LandUseItem> luRaster) {
+		long startTime = System.currentTimeMillis();
+
+		FileInputStream inputStream = null;
+		DataInputStream reader = null;
+
+		try {
+			inputStream = new FileInputStream(ModelConfig.SERIALIZED_LAND_COVER_FILE);
+			BufferedInputStream buffInputStream = new BufferedInputStream(inputStream);
+			buffInputStream.mark(8);
+			reader = new DataInputStream(buffInputStream);
+
+			while(reader.available() > 0) {
+				// Coordinates loop
+
+				int col = (int) reader.readDouble();
+				int row = (int) reader.readDouble();
+
+				Map<LandCoverType, LandCoverTile> landCoverAreas = new HashMap<LandCoverType, LandCoverTile>();
+				for (LandCoverType lcType: LandCoverType.values()) {
+					landCoverAreas.put(lcType, new LandCoverTile());
+				}
+
+				LandUseItem luItem = luRaster.get(new RasterKey(col, row));
+
+				// Protection class loop
+				for (int protIdx = 0; protIdx < NUM_PROTECTION_CLASSES; protIdx++) {
+
+					switch(protIdx) {
+					case CONVERTIBLE_IDX:
+						for (LandCoverType lcType : LandCoverType.values()) {
+							for (int age = 0; age < NUM_AGE_CLASSES; age++) {
+								double area = reader.readDouble();
+								if (area > 0)
+									landCoverAreas.get(lcType).setArea(LandProtectionType.CONVERTIBLE, age, area);
+							}
+						}
+						break;
+					case PROTECTED_IDX:
+						for (LandCoverType lcType : LandCoverType.values()) {
+							for (int age = 0; age < NUM_AGE_CLASSES; age++) {
+								double area = reader.readDouble();
+								if (area > 0)
+									landCoverAreas.get(lcType).setArea(LandProtectionType.PROTECTED, age, area);
+							}
+						}	
+						break;
+					case UNVAVAILABLE_IDX:
+						for (LandCoverType lcType : LandCoverType.values()) {
+							double area = reader.readDouble();
+							if (area > 0)
+								landCoverAreas.get(lcType).setArea(LandProtectionType.UNAVAILABLE, 0, area); // TODO Do we need to keep track of age?
+						}
+					}
+				}
+
+				luItem.overwriteLandCoverAreas(landCoverAreas);
+			}
+
+			reader.close();
+		}
+		catch (Exception e) {
+			LogWriter.printlnError("Problem reading data file " + ModelConfig.SERIALIZED_LAND_COVER_FILE);
+			LogWriter.print(e);
+			throw new RuntimeException(e);
+		}
+		finally {
+			if (inputStream != null) {
+				try {
+					inputStream.close();
+				} catch (IOException e) {
+					LogWriter.print(e);
+				}
+			}
+			if (reader != null) {
+				try {
+					reader.close();
+				} catch (IOException e) {
+					LogWriter.print(e);
+				}
+			}
+		}
+		LogWriter.println("Deserialized land cover " + ModelConfig.SERIALIZED_LAND_COVER_FILE + ", took " + (System.currentTimeMillis() - startTime) + " ms");
+	}
+
+}
diff --git a/src/ac/ed/lurg/landuse/LandUseItem.java b/src/ac/ed/lurg/landuse/LandUseItem.java
index c96f78659628c8e9e5e6414849904bb0f1c6185c..8385ddf874673efe33d23b8e337b656e074eb474 100644
--- a/src/ac/ed/lurg/landuse/LandUseItem.java
+++ b/src/ac/ed/lurg/landuse/LandUseItem.java
@@ -1,602 +1,461 @@
-package ac.ed.lurg.landuse;
-
-import java.io.Serializable;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import ac.ed.lurg.ModelConfig;
-import ac.ed.lurg.types.CropToDouble;
-import ac.ed.lurg.types.CropType;
-import ac.ed.lurg.types.LandCoverType;
-import ac.ed.lurg.types.LandProtectionType;
-import ac.ed.lurg.utils.Interpolator;
-import ac.ed.lurg.utils.LogWriter;
-import ac.sac.raster.InterpolatingRasterItem;
-
-public class LandUseItem implements InterpolatingRasterItem<LandUseItem>, Serializable {
-	private static final long serialVersionUID = 763526147224355158L;
-	
-	private Map<CropType, Intensity> intensityMap = new HashMap<CropType, Intensity>();
-	private Map<CropType, Double> cropFractions = new HashMap<CropType, Double>();
-	private Map<LandCoverType, LandCoverTile> landCoverAreas = new HashMap<LandCoverType, LandCoverTile>();
-	private double protectedFraction; // protected fraction of total area
-	
-	public LandUseItem() {
-		for (LandCoverType lcType : LandCoverType.values()) {
-			landCoverAreas.put(lcType, new LandCoverTile());
-		}
-	}
-	
-	public LandUseItem(LandCoverItem landCover) {
-		this();
-		if (landCover != null) {
-			setCropFraction(CropType.WHEAT, 0.5); // random start as don't have better data
-			setCropFraction(CropType.MAIZE, 0.5);
-			setProtectedFraction(landCover.getProtectedFraction());
-			addLandCoverTiles(landCover);
-			setUnavailableArea(landCover.getUnavailableFract());
-
-		}
-	}
-
-	public LandUseItem(LandUseItem luItemToCopy) {
-		this();
-		intensityMap.putAll(luItemToCopy.intensityMap);
-		cropFractions.putAll(luItemToCopy.cropFractions);
-		landCoverAreas.putAll(luItemToCopy.landCoverAreas);
-		protectedFraction = (luItemToCopy.protectedFraction);
-	}
-
-	public void overwriteLandCoverAreas(Map<LandCoverType, LandCoverTile> landCoverAreas) {
-		this.landCoverAreas = new HashMap<LandCoverType, LandCoverTile>();
-		this.landCoverAreas.putAll(landCoverAreas);
-	}
-
-	public void addLandCoverTiles(LandCoverItem landCover) {		
-		double protectableFraction = 0;
-		for (LandCoverType lcType : LandCoverType.getProtectibleTypes()) {
-			protectableFraction += landCover.getLandCoverFract(lcType);
-		}
-		// need to scale protectedFraction by protectableFraction and cap at 1.0. Gives fraction of protectable LC types to protect
-		double adjProtectedFraction = Math.min(1.0, protectedFraction / protectableFraction); 
-				
-		for (LandCoverType lcType : LandCoverType.values()) {
-			LandCoverTile tiles = landCoverAreas.get(lcType);
-			double totalArea = landCover.getLandCoverArea(lcType);
-			if (totalArea == 0) 
-				continue;
-
-			// static land covers
-			if (!lcType.isConvertible()) { 
-				tiles.addArea(LandProtectionType.UNAVAILABLE, totalArea); // Assumes land cover age is 0
-				continue;
-			}
-			// convertible land covers			
-			double protectedArea;
-			double unprotectedArea;
-			if (lcType.isProtectable()) {
-				protectedArea = totalArea * adjProtectedFraction;
-				unprotectedArea = totalArea * (1 - adjProtectedFraction);
-			} else {
-				protectedArea = 0;
-				unprotectedArea = totalArea;
-			}
-
-			Map<Integer, Double> ageMap = landCover.getLandUseAgeDist().get(lcType);
-			double totalProp = ageMap.values().stream().reduce(0.0,  Double::sum);
-			if (totalProp == 0.0) { // If no distribution given then assume land is in oldest age group
-				int maxAgeGroup = ageMap.keySet().stream().max(Integer::compare).get();
-				ageMap.put(maxAgeGroup, 1.0);
-			}
-			if (totalProp > 1.0) {
-				// normalise
-				for (Map.Entry<Integer, Double> entry : ageMap.entrySet()) {
-					ageMap.put(entry.getKey(), entry.getValue() / totalProp);
-				}
-			}
-			
-			final int AGE_GROUP_SIZE = 10;
-			// Assuming land cover area all in middle age group
-			for (Map.Entry<Integer, Double> ageEntry : ageMap.entrySet()) {
-				double prop = ageEntry.getValue();
-				if (prop == 0.0)
-					continue;
-				int ageGroup = ageEntry.getKey();
-				int minAge = (int) (ageGroup - 1) * AGE_GROUP_SIZE + 1;
-				int maxAge = minAge + (AGE_GROUP_SIZE - 1);
-				int midAge = minAge + AGE_GROUP_SIZE / 2;
-				
-				landCoverAreas.get(lcType).setArea(LandProtectionType.CONVERTIBLE, midAge, unprotectedArea * prop);
-				landCoverAreas.get(lcType).setArea(LandProtectionType.PROTECTED, midAge, protectedArea * prop);
-				/*
-				for (int a = minAge; a <= maxAge; a++) {
-					landCoverAreas.get(lcType).setArea(LandProtectionType.CONVERTIBLE, a, unprotectedArea * prop);
-					landCoverAreas.get(lcType).setArea(LandProtectionType.PROTECTED, a, protectedArea * prop);
-				}
-				*/
-				
-			}
-		}
-		
-		// Check that areas have been allocated correctly
-		Map<LandCoverType, Double> initAreas = new HashMap<LandCoverType, Double>();
-		for (LandCoverType lc : LandCoverType.values()) {
-			initAreas.put(lc, landCover.getLandCoverArea(lc));
-		}
-		runAreaCheck(initAreas);
-	}
-	
-
-	public void runAreaCheck(Map<LandCoverType, Double> lcAreas) {
-		final double THRESHOLD = 1e-7;
-		for (LandCoverType lcType : landCoverAreas.keySet()) {
-			double initArea = lcAreas.get(lcType);
-			double processedArea = getLandCoverArea(lcType);
-			if (Math.abs(initArea - processedArea) > THRESHOLD) {
-				LogWriter.printlnError("LandUseItem: processed area and initial area different: " + lcType.getName() + " " + processedArea + ", " + initArea);
-			}
-		}
-	}
-	
-	public void setUnavailableArea(double unavailF) {
-		double barrenAndUrban = getLandCoverArea(LandCoverType.BARREN) + getLandCoverArea(LandCoverType.URBAN);
-		double unavailableArea = unavailF * getTotalLandCoverArea();
-		double shortfall = Math.max(0.0, unavailableArea - barrenAndUrban);
-		if (shortfall == 0.0)
-			return;
-
-		double convertibleNatural = getConvertibleLandCoverArea(LandCoverType.NATURAL);
-		double protectedNatural = getProtectedLandCoverArea(LandCoverType.NATURAL);
-		double convertibleTimberF = getConvertibleLandCoverArea(LandCoverType.TIMBER_FOREST);
-		double convertiblePasture = getConvertibleLandCoverArea(LandCoverType.PASTURE);
-		double convertibleCropland = getConvertibleLandCoverArea(LandCoverType.CROPLAND);
-		
-		double unavailFromConvertibleNatural = Math.min(shortfall, convertibleNatural);
-		shortfall -= unavailFromConvertibleNatural;
-		if (unavailFromConvertibleNatural > 0) {
-			landCoverAreas.get(LandCoverType.NATURAL).addArea(LandProtectionType.UNAVAILABLE, unavailFromConvertibleNatural);
-			landCoverAreas.get(LandCoverType.NATURAL).removeArea(LandProtectionType.CONVERTIBLE, unavailFromConvertibleNatural);
-		}
-		
-		double unavailFromProtectedNatural = Math.min(shortfall, protectedNatural);
-		shortfall -= unavailFromProtectedNatural;
-		if (unavailFromProtectedNatural > 0) {
-			landCoverAreas.get(LandCoverType.NATURAL).addArea(LandProtectionType.UNAVAILABLE, unavailFromProtectedNatural);
-			landCoverAreas.get(LandCoverType.NATURAL).removeArea(LandProtectionType.PROTECTED, unavailFromProtectedNatural);
-		}
-		
-		double unavailFromTimberF = Math.min(shortfall, convertibleTimberF);
-		shortfall -= unavailFromTimberF;
-		if (unavailFromTimberF > 0) {
-			landCoverAreas.get(LandCoverType.TIMBER_FOREST).addArea(LandProtectionType.UNAVAILABLE, unavailFromTimberF);
-			landCoverAreas.get(LandCoverType.TIMBER_FOREST).removeArea(LandProtectionType.CONVERTIBLE, unavailFromTimberF);
-		}
-		
-		double unavailFromPasture = Math.min(shortfall, convertiblePasture);
-		shortfall -= unavailFromPasture;
-		if (unavailFromPasture > 0) {
-			landCoverAreas.get(LandCoverType.PASTURE).addArea(LandProtectionType.UNAVAILABLE, unavailFromPasture);
-			landCoverAreas.get(LandCoverType.PASTURE).removeArea(LandProtectionType.CONVERTIBLE, unavailFromPasture);
-		}
-		
-		double unavailFromCropland = Math.min(shortfall, convertibleCropland);
-		shortfall -= unavailFromCropland;
-		if (unavailFromCropland > 0) {
-			landCoverAreas.get(LandCoverType.CROPLAND).addArea(LandProtectionType.UNAVAILABLE, unavailFromCropland);
-			landCoverAreas.get(LandCoverType.CROPLAND).removeArea(LandProtectionType.CONVERTIBLE, unavailFromCropland);
-		}
-		
-		if (shortfall > 0) {
-			LogWriter.printlnWarning("LandUseItem.setUnavailableArea insufficient area to make unavailable");
-		}
-	}
-	
-	public Map<LandCoverType, LandCoverTile> getLandCoverTiles() {
-		return landCoverAreas;
-	}
-	
-	public void setProtectedFraction(double protFrac) {
-		if (!ModelConfig.PROTECTED_AREAS_ENABLED) {
-			this.protectedFraction = 0;
-			return;
-		}
-		if (!Double.isNaN(ModelConfig.CONSTANT_PROTECTED_AREA_RATE)) {
-			this.protectedFraction = ModelConfig.CONSTANT_PROTECTED_AREA_RATE; // TODO is this a fraction or area?
-		} else {
-			this.protectedFraction = Math.max(protFrac, ModelConfig.MIN_NATURAL_RATE);
-		}
-	}
-	
-	public void setProtectedLandCoverArea(LandCoverType lcType, double area) {
-		landCoverAreas.get(lcType).setArea(LandProtectionType.PROTECTED, 0, area);
-	}
-	
-	public double getProtectedFraction() {
-		return protectedFraction;
-	}
-
-	public double getTotalProtectedArea() {
-		if(ModelConfig.PROTECTED_AREAS_ENABLED)
-			return landCoverAreas.values().stream().mapToDouble(o -> o.getTotalArea(LandProtectionType.PROTECTED)).sum();
-		else 
-			return 0.0;	
-	}
-	
-	public double getProtectedLandCoverArea(LandCoverType lcType) {
-		return landCoverAreas.get(lcType).getTotalArea(LandProtectionType.PROTECTED);
-	}
-
-	public double getSuitableArea() {
-		double suitableArea = 0;
-		for (LandCoverType lcType : LandCoverType.getConvertibleTypes()) {
-			suitableArea += getConvertibleLandCoverArea(lcType);
-		}
-		return suitableArea;
-	}
-	
-	/** move areas from one land cover to another, return any residual not possible */   
-	public double moveAreas(LandCoverType toType, LandCoverType fromType, double area) {
-		if (area == 0)
-			return 0;
-		
-        double prevFrom = getConvertibleLandCoverArea(fromType);
-        double areaMoved;
-       
-        areaMoved = Math.min(area, prevFrom);
-
-        if (Math.abs(area - areaMoved) / area > 1e-6) {
-        	throw new RuntimeException("Move unprotected area shortfall. Should not happen due to GAMS constraint.");
-        }
-           
-		// Note if toType == fromType, area will be removed from fromType and added back in with age 0
-		LandCoverTile toTiles = landCoverAreas.get(toType);
-		LandCoverTile fromTiles = landCoverAreas.get(fromType);
-
-		fromTiles.removeArea(LandProtectionType.CONVERTIBLE, area);	
-		toTiles.addArea(LandProtectionType.CONVERTIBLE, area);
-   
-        return area - areaMoved;
-	}
-	
-	public double getLandCoverArea(LandCoverType c) {
-		Double d = landCoverAreas.get(c).getTotalArea();
-		return d == null ? 0.0 : d;
-	}
-	
-	public double getLandCoverFract(LandCoverType c) {
-		double totalArea = getTotalLandCoverArea();
-		return totalArea==0 ? 0.0 : getLandCoverArea(c) / totalArea;
-	}
-	
-	public void setConvertibleLandCoverArea(LandCoverType c, double d) {
-		if (Double.isNaN(d) || Double.isInfinite(d))
-			throw new RuntimeException("AreasItem for " + c + " is " + d);
-
-		double landCover = (d < 0.0) ? 0.0 : d;
-		
-		landCoverAreas.get(c).removeAllArea();
-		landCoverAreas.get(c).addArea(LandProtectionType.CONVERTIBLE, landCover);;
-	}
-	
-	public double getTotalLandCoverArea() {
-		double d = 0;
-		for (LandCoverType l : LandCoverType.values()) 
-			d += getLandCoverArea(l);
-	
-		return d;
-	}
-	
-	public Map<LandCoverType, Double> getLandCoverAreaMap() {
-		Map<LandCoverType, Double> areaMap = new HashMap<LandCoverType, Double>();
-		for (LandCoverType lcType : LandCoverType.values()) {
-			areaMap.put(lcType, getLandCoverArea(lcType));
-		}
-		return areaMap;
-	}
-	
-	public double getTotalConvertibleNaturalArea() {	
-		double unprotectedNatural = 0;
-		for (LandCoverType landType : LandCoverType.getNaturalTypes()) {
-			unprotectedNatural += landCoverAreas.get(landType).getTotalArea(LandProtectionType.CONVERTIBLE);
-		}
-		return unprotectedNatural;
-	}
-	
-	public double getConvertibleLandCoverArea(LandCoverType c) {
-		return landCoverAreas.get(c).getTotalArea(LandProtectionType.CONVERTIBLE);
-	}
-	
-	
-	public void updateProtectedFraction(int year) {
-		if (!ModelConfig.FORCE_PROTECTED_AREAS)
-			return;
-			
-		if (year >= ModelConfig.FORCE_PROTECTED_AREAS_START_YEAR && year < ModelConfig.FORCE_PROTECTED_AREAS_END_YEAR) 
-			protectedFraction = 1.0 / (ModelConfig.FORCE_PROTECTED_AREAS_END_YEAR - year);
-		
-		if (year >= ModelConfig.FORCE_PROTECTED_AREAS_END_YEAR)
-			protectedFraction = 1.0;		
-	}
-	
-	public Map<CropType, Intensity> getIntensityMap() {
-		return intensityMap;
-	}
-	
-	public Intensity getIntensity(CropType crop) {
-		return intensityMap.get(crop);
-	}
-	
-	public void setIntensity(CropType crop, Intensity intensityData) {
-		intensityMap.put(crop, intensityData);
-	}
-	
-	/** Fertiliser rate in kg/ha */
-	public double getFertiliserRate(CropType crop) {
-		Intensity i = getIntensity(crop);
-		return (i == null) ? 0 : i.getFertiliserAmount();
-	}
-	
-	/** Fertiliser amount in kt (1000 tonnes), for this location */
-	public double getFertiliserAmount(CropType c) {
-		double rate = getFertiliserRate(c);
-		double area = getCropArea(c);
-		return rate * area;
-	}
-	
-	public double getFertiliserAverageRate(CropType... crops) {
-		double fertTotal = 0;
-		double areaTotal = 0;
-		
-		for (CropType c : crops) {
-			fertTotal += getFertiliserAmount(c);
-			areaTotal += getCropArea(c);
-		}
-	
-		return areaTotal > 0 ? fertTotal / areaTotal : 0;
-	}
-
-	public static double getFertiliserTotal(Collection<? extends LandUseItem> items, CropType... crops) {			
-		double total = 0;
-		for (LandUseItem a : items) {
-			if (a == null)
-				continue;
-			
-			for (CropType c : crops)
-				total += a.getFertiliserAmount(c);
-		}
-			
-		return total;
-	}
-
-	public static double getFertiliserTotal(Collection<? extends LandUseItem> items, Collection<CropType> crops) {
-		return getFertiliserTotal(items, crops.toArray(new CropType[crops.size()]));
-	}
-	
-	/** Irrigation Intensity (unit less) */
-	public double getIrrigationIntensity(CropType crop) {
-		Intensity i = getIntensity(crop);
-		return (i == null) ? 0 : i.getIrrigationIntensity();
-	}
-	
-	/** Irrigation rate in litre/m2 */
-	public double getIrrigationRate(CropType crop) {
-		Intensity i = getIntensity(crop);
-		
-		return (i == null) ? 0 : i.getIrrigationRate();
-	}
-
-	/** Irrigation amount in km3 or 10^9 m3, for this location */
-	public double getIrrigationAmount(CropType c) {
-		double rate = getIrrigationRate(c);
-		double area = getCropArea(c);
-		return rate * area * 0.01;  // rate(10^-3m or mm or l/m2) * area(10^6ha) = 10^3 m.ha = 10^7 m3 = 1/100 km3
-	}
-
-	/** Irrigation Intensity (unit less) */
-	public double getIrrigationAverageIntensity(CropType... crops) {
-		double irrigTotal = 0;
-		double areaTotal = 0;
-		
-		for (CropType c : crops) {
-			double area = getCropArea(c);
-			irrigTotal += getIrrigationIntensity(c) * area;
-			areaTotal += area;
-		}
-		
-		return areaTotal > 0 ? irrigTotal / areaTotal : 0;
-	}
-	
-	/** Irrigation amount in km3, for this location */
-	public static double getIrrigationTotal(Collection<? extends LandUseItem> items, CropType... crops) {			
-		double total = 0;
-		for (LandUseItem a : items) {
-			if (a == null)
-				continue;
-			
-			for (CropType c : crops)
-				total += a.getIrrigationAmount(c);
-		}
-			
-		return total;
-	}
-
-	public static double getIrrigationTotal(Collection<? extends LandUseItem> items, Collection<CropType> crops) {
-		return getIrrigationTotal(items, crops.toArray(new CropType[crops.size()]));
-	}
-
-	public double getCropArea(CropType c) {
-		Double d = getLandCoverArea(LandCoverType.CROPLAND) * getCropFraction(c);
-		return d;
-	}
-	
-	public double getCropFraction(CropType c) {
-		Double d = cropFractions.get(c);
-		return d == null ? 0.0 : d;
-	}
-
-	public double getCropFraction(CropType... cropsToFind) {
-		double totalFract = 0;
-		for (CropType crop : cropsToFind) {
-			totalFract += getCropFraction(crop);
-		}
-		return totalFract;
-	}
-	
-	public Map<CropType, Double> getCropFractionMap() {
-		return cropFractions;
-	}
-
-	public void setCropFraction(CropType c, double areaFract) {
-		cropFractions.put(c, areaFract);
-	}
-
-	public static double getAbandonedPasture(Collection<? extends LandUseItem> landUses) {
-		double total = 0;
-		for (LandUseItem a : landUses) {
-			if (a!=null) {
-				Double d = a.getLandCoverArea(LandCoverType.PASTURE);
-				Intensity intensity = a.getIntensity(CropType.PASTURE);
-				if (intensity!=null && d!=null && intensity.getFertiliserAmount()==0 && intensity.getIrrigationRate()==0 && intensity.getOtherIntensity()==0)
-					total += d;
-			}
-		}
-		return total;
-	}
-	
-	public static double getSuitableTotal(Collection<? extends LandUseItem> landUses, int year) {
-		double total = 0;
-		for (LandUseItem a : landUses) {
-			if (a!=null) {
-				Double suitable = a.getSuitableArea();
-					total += suitable;
-			}
-		}
-		return total;
-	}
-		
-	public CropToDouble getCropChanges(LandUseItem prevAreaAggItem) {
-		CropToDouble changes = new CropToDouble();
-		
-		for (CropType c : CropType.getCropsLessPasture()) {
-			double change = getCropArea(c) - prevAreaAggItem.getCropArea(c);
-			changes.put(c, change);
-		}
-		
-		return changes;
-	}
-	
-	public void doForestRotation(int rotationAge) {
-		LandCoverTile tiles = landCoverAreas.get(LandCoverType.TIMBER_FOREST);
-		for (int a=rotationAge; a<=LandCoverTile.getMaxAgeBin(); a++) {
-			tiles.resetAreaForAge(LandProtectionType.CONVERTIBLE, a);
-		}	
-	}
-	
-	public void incrementTilesAge() {
-		for (LandCoverTile tile : landCoverAreas.values()) {
-			tile.incrementAge();
-		}
-	}
-
-	private boolean isZeroOrNull(Double d) {
-		return d == null || d == 0;
-	}
-	 
-	@Override
-	public void interpolateAll(LandUseItem fromItem, LandUseItem toItem, double factor) {		
-		cropFractions = new HashMap<CropType, Double>();
-		landCoverAreas = new HashMap<LandCoverType, LandCoverTile>();
-
-		Double fromCropCover = fromItem.landCoverAreas.get(LandCoverType.CROPLAND).getTotalArea();
-		Double toCropCover = toItem.landCoverAreas.get(LandCoverType.CROPLAND).getTotalArea();
-
-		if (!isZeroOrNull(fromCropCover) && isZeroOrNull(toCropCover)) { // if start with crop but end with none, take starting crop fractions
-			cropFractions.putAll(fromItem.cropFractions);
-			intensityMap.putAll(fromItem.intensityMap);
-		}
-		else if (isZeroOrNull(fromCropCover) && !isZeroOrNull(toCropCover)) { // if start with no crop but end with some, take end crop fractions
-			cropFractions.putAll(toItem.cropFractions);
-			intensityMap.putAll(toItem.intensityMap);
-		}
-		else { // otherwise we need to interpolate crop fractions
-			for (CropType crop : CropType.values()) {
-				Double from = fromItem.cropFractions.get(crop);
-				Double to = toItem.cropFractions.get(crop);
-				Double d = Interpolator.interpolate(from, to, factor);
-				cropFractions.put(crop, d);
-				
-				Intensity fromIntensity = fromItem.intensityMap.get(crop);
-				Intensity toIntensity = toItem.intensityMap.get(crop);
-				Intensity interpolateIntensity = toIntensity;  // might still be null
-				
-				if (fromIntensity != null && toIntensity != null)
-					interpolateIntensity = new Intensity(fromIntensity, toIntensity, factor);  // both non-null really interpolate
-				else if (fromIntensity != null)
-					interpolateIntensity = fromIntensity; // just fromIntensity non-null
-				
-				intensityMap.put(crop, interpolateIntensity);
-			}
-		}
-		
-		for (LandCoverType landCover : LandCoverType.values()) {
-			for (int age = 0; age <= LandCoverTile.getMaxAgeBin(); age++) {
-				Double from = fromItem.landCoverAreas.get(landCover).getArea(LandProtectionType.CONVERTIBLE, age);
-				Double to = toItem.landCoverAreas.get(landCover).getArea(LandProtectionType.CONVERTIBLE, age);
-				Double d = Interpolator.interpolate(from, to, factor);
-				LandCoverTile tile = landCoverAreas.computeIfAbsent(landCover, k -> new LandCoverTile());
-				tile.setArea(LandProtectionType.CONVERTIBLE, age, d);
-			}
-		}
-		
-		for (LandCoverType landCover : LandCoverType.values()) {
-			Double from = fromItem.landCoverAreas.get(landCover).getTotalArea(LandProtectionType.PROTECTED);
-			Double to = toItem.landCoverAreas.get(landCover).getTotalArea(LandProtectionType.PROTECTED);
-			Double d = Interpolator.interpolate(from, to, factor);
-			LandCoverTile tile = landCoverAreas.computeIfAbsent(landCover, k -> new LandCoverTile());
-			tile.addArea(LandProtectionType.PROTECTED, d);
-		}
-	}
-	
-	public static double getTotalLandCover(Collection<? extends LandUseItem> landUses, LandCoverType landCover) {
-		double total = 0;
-		for (LandUseItem a : landUses) {
-			if (a!=null) {
-				Double d = a.getLandCoverArea(landCover);
-				if (d!=null)
-					total += d;
-			}
-		}
-		return total;
-	}
-	
-	public static double getTotalLandArea(Collection<? extends LandUseItem> landUses) {
-		double total = 0;
-		for (LandUseItem a : landUses) {
-			if (a!=null) {
-				Double d = a.getTotalLandCoverArea();
-				if (d!=null)
-					total += d;
-			}
-		}
-		return total;
-	}
-
-	public static double getTotalCropArea(Collection<LandUseItem> landUses, CropType crop) {
-		double total = 0;
-		for (LandUseItem a : landUses)
-			total += a.getCropArea(crop);
-		
-		return total;
-	}
-
-	@Override
-	public String toString() {
-		Map<LandCoverType, Double> convertibleAreas = new HashMap<LandCoverType, Double>();
-		Map<LandCoverType, Double> protectedAreas = new HashMap<LandCoverType, Double>();
-		for (LandCoverType lcType : LandCoverType.values()) {
-			convertibleAreas.put(lcType, landCoverAreas.get(lcType).getTotalArea(LandProtectionType.CONVERTIBLE));
-			protectedAreas.put(lcType, landCoverAreas.get(lcType).getTotalArea(LandProtectionType.PROTECTED));
-		}
-		return "LandUseItem: [landCoverAreas=" + convertibleAreas + ", protectedArea=" + protectedAreas + "]";
-	}
+package ac.ed.lurg.landuse;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import ac.ed.lurg.ModelConfig;
+import ac.ed.lurg.types.CropToDouble;
+import ac.ed.lurg.types.CropType;
+import ac.ed.lurg.types.LandCoverType;
+import ac.ed.lurg.types.LandProtectionType;
+import ac.ed.lurg.utils.Interpolator;
+import ac.sac.raster.InterpolatingRasterItem;
+
+public class LandUseItem implements InterpolatingRasterItem<LandUseItem>, Serializable {
+	private static final long serialVersionUID = 763526147224355158L;
+	
+	private Map<CropType, Intensity> intensityMap = new HashMap<CropType, Intensity>();
+	private Map<CropType, Double> cropFractions = new HashMap<CropType, Double>();
+	private Map<LandCoverType, LandCoverTile> landCoverAreas = new HashMap<LandCoverType, LandCoverTile>();
+	private double protectedFraction; // protected fraction of total area
+	
+	public LandUseItem() {
+		for (LandCoverType lcType : LandCoverType.values()) {
+			landCoverAreas.put(lcType, new LandCoverTile());
+		}
+	}
+
+	public LandUseItem(LandCoverItem landCover) {
+		this();
+		if (landCover != null) {
+			setCropFraction(CropType.WHEAT, 0.5); // random start as don't have better data
+			setCropFraction(CropType.MAIZE, 0.5);
+			setProtectedFraction(landCover.getProtectedFraction());
+			this.landCoverAreas.putAll(landCover.getLandCoverTiles());
+		}
+	}
+
+	public LandUseItem(LandUseItem luItemToCopy) {
+		this();
+		intensityMap.putAll(luItemToCopy.intensityMap);
+		cropFractions.putAll(luItemToCopy.cropFractions);
+		landCoverAreas.putAll(luItemToCopy.landCoverAreas);
+		protectedFraction = (luItemToCopy.protectedFraction);
+	}
+
+	public void overwriteLandCoverAreas(Map<LandCoverType, LandCoverTile> landCoverAreas) {
+		this.landCoverAreas = new HashMap<LandCoverType, LandCoverTile>();
+		this.landCoverAreas.putAll(landCoverAreas);
+	}
+	
+	public Map<LandCoverType, LandCoverTile> getLandCoverTiles() {
+		return landCoverAreas;
+	}
+	
+	public void setProtectedFraction(double protFrac) {
+		if (!ModelConfig.PROTECTED_AREAS_ENABLED) {
+			this.protectedFraction = 0;
+			return;
+		}
+		if (!Double.isNaN(ModelConfig.CONSTANT_PROTECTED_AREA_RATE)) {
+			this.protectedFraction = ModelConfig.CONSTANT_PROTECTED_AREA_RATE;
+		} else {
+			this.protectedFraction = Math.max(protFrac, ModelConfig.MIN_NATURAL_RATE);
+		}
+	}
+
+	public double getProtectedFraction() {
+		return protectedFraction;
+	}
+	
+	public double getLandCoverArea(LandCoverType lcType, LandProtectionType lpType) {
+		return landCoverAreas.get(lcType).getTotalArea(lpType);
+	}
+	
+	public double getLandCoverArea(LandCoverType lcType) {
+		Double d = landCoverAreas.get(lcType).getTotalArea();
+		return d == null ? 0.0 : d;
+	}
+	
+	public double getTotalLandCoverArea() {
+		double d = 0;
+		for (LandCoverType l : LandCoverType.values()) 
+			d += getLandCoverArea(l);
+	
+		return d;
+	}
+	
+	public double getTotalLandCoverArea(LandProtectionType lpType) {
+		double d = 0;
+		for (LandCoverType l : LandCoverType.values()) 
+			d += getLandCoverArea(l, lpType);
+	
+		return d;
+	}
+	
+	public double getLandCoverFract(LandCoverType c) {
+		double totalArea = getTotalLandCoverArea();
+		return totalArea==0 ? 0.0 : getLandCoverArea(c) / totalArea;
+	}
+
+	public double getSuitableArea() {
+		double suitableArea = 0;
+		for (LandCoverType lcType : LandCoverType.getConvertibleTypes()) {
+			suitableArea += getLandCoverArea(lcType, LandProtectionType.CONVERTIBLE);
+		}
+		return suitableArea;
+	}
+	
+	/** move areas from one land cover to another, return any residual not possible */   
+	public double moveAreas(LandCoverType toType, LandCoverType fromType, double area) {
+		if (area == 0)
+			return 0;
+		
+        double prevFrom = getLandCoverArea(fromType, LandProtectionType.CONVERTIBLE);
+        double areaMoved;
+       
+        areaMoved = Math.min(area, prevFrom);
+
+        if (Math.abs(area - areaMoved) / area > 1e-6) {
+        	throw new RuntimeException("Move unprotected area shortfall. Should not happen due to GAMS constraint.");
+        }
+           
+		// Note if toType == fromType, area will be removed from fromType and added back in with age 0
+		LandCoverTile toTiles = landCoverAreas.get(toType);
+		LandCoverTile fromTiles = landCoverAreas.get(fromType);
+
+		fromTiles.removeArea(LandProtectionType.CONVERTIBLE, areaMoved);	
+		if (ModelConfig.IS_CALIBRATION_RUN) {
+			toTiles.addArea(LandProtectionType.CONVERTIBLE, areaMoved); // preserve age distribution
+		} else {
+			toTiles.addArea(LandProtectionType.CONVERTIBLE, 0, areaMoved); // new area starts at age=0
+		}
+   
+        return area - areaMoved;
+	}
+
+	public void setLandCoverArea(LandCoverType c, LandProtectionType p, double d) {
+		if (Double.isNaN(d) || Double.isInfinite(d))
+			throw new RuntimeException("AreasItem for " + c + " is " + d);
+
+		double landCover = (d < 0.0) ? 0.0 : d;
+		
+		landCoverAreas.get(c).removeAllArea(p);
+		landCoverAreas.get(c).addArea(p, 0, landCover);
+	}
+
+	public Map<LandCoverType, Double> getLandCoverAreaMap() {
+		Map<LandCoverType, Double> areaMap = new HashMap<LandCoverType, Double>();
+		for (LandCoverType lcType : LandCoverType.values()) {
+			areaMap.put(lcType, getLandCoverArea(lcType));
+		}
+		return areaMap;
+	}
+	
+	public void updateProtectedFraction(int year) {
+		if (!ModelConfig.FORCE_PROTECTED_AREAS)
+			return;
+			
+		if (year >= ModelConfig.FORCE_PROTECTED_AREAS_START_YEAR && year < ModelConfig.FORCE_PROTECTED_AREAS_END_YEAR) 
+			protectedFraction = 1.0 / (ModelConfig.FORCE_PROTECTED_AREAS_END_YEAR - year);
+		
+		if (year >= ModelConfig.FORCE_PROTECTED_AREAS_END_YEAR)
+			protectedFraction = 1.0;
+		
+		updateProtectedArea();	
+	}
+	
+	private void updateProtectedArea() {
+		
+		double previousProtectedTotal = landCoverAreas.values().stream().mapToDouble(o -> o.getTotalArea(LandProtectionType.PROTECTED)).sum();
+		double newProtectedTotal = protectedFraction * getTotalLandCoverArea();
+		double factor = newProtectedTotal / previousProtectedTotal;
+		
+		for (LandCoverType lcType : LandCoverType.values()) {
+			double prevArea = getLandCoverArea(lcType, LandProtectionType.PROTECTED);
+			double newArea = prevArea * factor;
+			if (newArea > prevArea) {
+				landCoverAreas.get(lcType).moveArea(LandProtectionType.CONVERTIBLE, LandProtectionType.PROTECTED, newArea);
+			} else {
+				landCoverAreas.get(lcType).moveArea(LandProtectionType.PROTECTED, LandProtectionType.CONVERTIBLE, newArea);
+			}			
+		}		
+	}
+	
+	public Map<CropType, Intensity> getIntensityMap() {
+		return intensityMap;
+	}
+	
+	public Intensity getIntensity(CropType crop) {
+		return intensityMap.get(crop);
+	}
+	
+	public void setIntensity(CropType crop, Intensity intensityData) {
+		intensityMap.put(crop, intensityData);
+	}
+	
+	/** Fertiliser rate in kg/ha */
+	public double getFertiliserRate(CropType crop) {
+		Intensity i = getIntensity(crop);
+		return (i == null) ? 0 : i.getFertiliserAmount();
+	}
+	
+	/** Fertiliser amount in kt (1000 tonnes), for this location */
+	public double getFertiliserAmount(CropType c) {
+		double rate = getFertiliserRate(c);
+		double area = getCropArea(c);
+		return rate * area;
+	}
+	
+	public double getFertiliserAverageRate(CropType... crops) {
+		double fertTotal = 0;
+		double areaTotal = 0;
+		
+		for (CropType c : crops) {
+			fertTotal += getFertiliserAmount(c);
+			areaTotal += getCropArea(c);
+		}
+	
+		return areaTotal > 0 ? fertTotal / areaTotal : 0;
+	}
+
+	public static double getFertiliserTotal(Collection<? extends LandUseItem> items, CropType... crops) {			
+		double total = 0;
+		for (LandUseItem a : items) {
+			if (a == null)
+				continue;
+			
+			for (CropType c : crops)
+				total += a.getFertiliserAmount(c);
+		}
+			
+		return total;
+	}
+
+	public static double getFertiliserTotal(Collection<? extends LandUseItem> items, Collection<CropType> crops) {
+		return getFertiliserTotal(items, crops.toArray(new CropType[crops.size()]));
+	}
+	
+	/** Irrigation Intensity (unit less) */
+	public double getIrrigationIntensity(CropType crop) {
+		Intensity i = getIntensity(crop);
+		return (i == null) ? 0 : i.getIrrigationIntensity();
+	}
+	
+	/** Irrigation rate in litre/m2 */
+	public double getIrrigationRate(CropType crop) {
+		Intensity i = getIntensity(crop);
+		
+		return (i == null) ? 0 : i.getIrrigationRate();
+	}
+
+	/** Irrigation amount in km3 or 10^9 m3, for this location */
+	public double getIrrigationAmount(CropType c) {
+		double rate = getIrrigationRate(c);
+		double area = getCropArea(c);
+		return rate * area * 0.01;  // rate(10^-3m or mm or l/m2) * area(10^6ha) = 10^3 m.ha = 10^7 m3 = 1/100 km3
+	}
+
+	/** Irrigation Intensity (unit less) */
+	public double getIrrigationAverageIntensity(CropType... crops) {
+		double irrigTotal = 0;
+		double areaTotal = 0;
+		
+		for (CropType c : crops) {
+			double area = getCropArea(c);
+			irrigTotal += getIrrigationIntensity(c) * area;
+			areaTotal += area;
+		}
+		
+		return areaTotal > 0 ? irrigTotal / areaTotal : 0;
+	}
+	
+	/** Irrigation amount in km3, for this location */
+	public static double getIrrigationTotal(Collection<? extends LandUseItem> items, CropType... crops) {			
+		double total = 0;
+		for (LandUseItem a : items) {
+			if (a == null)
+				continue;
+			
+			for (CropType c : crops)
+				total += a.getIrrigationAmount(c);
+		}
+			
+		return total;
+	}
+
+	public static double getIrrigationTotal(Collection<? extends LandUseItem> items, Collection<CropType> crops) {
+		return getIrrigationTotal(items, crops.toArray(new CropType[crops.size()]));
+	}
+
+	public double getCropArea(CropType c) {
+		Double d = getLandCoverArea(LandCoverType.CROPLAND) * getCropFraction(c);
+		return d;
+	}
+	
+	public double getCropFraction(CropType c) {
+		Double d = cropFractions.get(c);
+		return d == null ? 0.0 : d;
+	}
+
+	public double getCropFraction(CropType... cropsToFind) {
+		double totalFract = 0;
+		for (CropType crop : cropsToFind) {
+			totalFract += getCropFraction(crop);
+		}
+		return totalFract;
+	}
+	
+	public Map<CropType, Double> getCropFractionMap() {
+		return cropFractions;
+	}
+
+	public void setCropFraction(CropType c, double areaFract) {
+		cropFractions.put(c, areaFract);
+	}
+
+	public static double getAbandonedPasture(Collection<? extends LandUseItem> landUses) {
+		double total = 0;
+		for (LandUseItem a : landUses) {
+			if (a!=null) {
+				Double d = a.getLandCoverArea(LandCoverType.PASTURE);
+				Intensity intensity = a.getIntensity(CropType.PASTURE);
+				if (intensity!=null && d!=null && intensity.getFertiliserAmount()==0 && intensity.getIrrigationRate()==0 && intensity.getOtherIntensity()==0)
+					total += d;
+			}
+		}
+		return total;
+	}
+	
+	public static double getSuitableTotal(Collection<? extends LandUseItem> landUses, int year) {
+		double total = 0;
+		for (LandUseItem a : landUses) {
+			if (a!=null) {
+				Double suitable = a.getSuitableArea();
+					total += suitable;
+			}
+		}
+		return total;
+	}
+		
+	public CropToDouble getCropChanges(LandUseItem prevAreaAggItem) {
+		CropToDouble changes = new CropToDouble();
+		
+		for (CropType c : CropType.getCropsLessPasture()) {
+			double change = getCropArea(c) - prevAreaAggItem.getCropArea(c);
+			changes.put(c, change);
+		}
+		
+		return changes;
+	}
+
+	public void incrementTilesAge() {
+		for (LandCoverTile tile : landCoverAreas.values()) {
+			tile.cleanUp();
+			tile.incrementAge();
+		}
+	}
+
+	private boolean isZeroOrNull(Double d) {
+		return d == null || d == 0;
+	}
+	 
+	@Override
+	public void interpolateAll(LandUseItem fromItem, LandUseItem toItem, double factor) {		
+		cropFractions = new HashMap<CropType, Double>();
+		landCoverAreas = new HashMap<LandCoverType, LandCoverTile>();
+
+		Double fromCropCover = fromItem.landCoverAreas.get(LandCoverType.CROPLAND).getTotalArea();
+		Double toCropCover = toItem.landCoverAreas.get(LandCoverType.CROPLAND).getTotalArea();
+
+		if (!isZeroOrNull(fromCropCover) && isZeroOrNull(toCropCover)) { // if start with crop but end with none, take starting crop fractions
+			cropFractions.putAll(fromItem.cropFractions);
+			intensityMap.putAll(fromItem.intensityMap);
+		}
+		else if (isZeroOrNull(fromCropCover) && !isZeroOrNull(toCropCover)) { // if start with no crop but end with some, take end crop fractions
+			cropFractions.putAll(toItem.cropFractions);
+			intensityMap.putAll(toItem.intensityMap);
+		}
+		else { // otherwise we need to interpolate crop fractions
+			for (CropType crop : CropType.values()) {
+				Double from = fromItem.cropFractions.get(crop);
+				Double to = toItem.cropFractions.get(crop);
+				Double d = Interpolator.interpolate(from, to, factor);
+				cropFractions.put(crop, d);
+				
+				Intensity fromIntensity = fromItem.intensityMap.get(crop);
+				Intensity toIntensity = toItem.intensityMap.get(crop);
+				Intensity interpolateIntensity = toIntensity;  // might still be null
+				
+				if (fromIntensity != null && toIntensity != null)
+					interpolateIntensity = new Intensity(fromIntensity, toIntensity, factor);  // both non-null really interpolate
+				else if (fromIntensity != null)
+					interpolateIntensity = fromIntensity; // just fromIntensity non-null
+				
+				intensityMap.put(crop, interpolateIntensity);
+			}
+		}
+		
+		for (LandCoverType landCover : LandCoverType.values()) {
+			for (int age : landCoverAreas.get(landCover).getAgeKeys()) {
+				Double from = fromItem.landCoverAreas.get(landCover).getArea(LandProtectionType.CONVERTIBLE, age);
+				Double to = toItem.landCoverAreas.get(landCover).getArea(LandProtectionType.CONVERTIBLE, age);
+				Double d = Interpolator.interpolate(from, to, factor);
+				LandCoverTile tile = landCoverAreas.computeIfAbsent(landCover, k -> new LandCoverTile());
+				tile.setArea(LandProtectionType.CONVERTIBLE, age, d);
+			}
+		}
+		
+		for (LandCoverType landCover : LandCoverType.values()) {
+			Double from = fromItem.landCoverAreas.get(landCover).getTotalArea(LandProtectionType.PROTECTED);
+			Double to = toItem.landCoverAreas.get(landCover).getTotalArea(LandProtectionType.PROTECTED);
+			Double d = Interpolator.interpolate(from, to, factor);
+			LandCoverTile tile = landCoverAreas.computeIfAbsent(landCover, k -> new LandCoverTile());
+			tile.addArea(LandProtectionType.PROTECTED, 0, d);
+		}
+	}
+	
+	public static double getTotalLandCover(Collection<? extends LandUseItem> landUses, LandCoverType landCover) {
+		double total = 0;
+		for (LandUseItem a : landUses) {
+			if (a!=null) {
+				Double d = a.getLandCoverArea(landCover);
+				if (d!=null)
+					total += d;
+			}
+		}
+		return total;
+	}
+	
+	public static double getTotalLandArea(Collection<? extends LandUseItem> landUses) {
+		double total = 0;
+		for (LandUseItem a : landUses) {
+			if (a!=null) {
+				Double d = a.getTotalLandCoverArea();
+				if (d!=null)
+					total += d;
+			}
+		}
+		return total;
+	}
+
+	public static double getTotalCropArea(Collection<LandUseItem> landUses, CropType crop) {
+		double total = 0;
+		for (LandUseItem a : landUses)
+			total += a.getCropArea(crop);
+		
+		return total;
+	}
+
+	@Override
+	public String toString() {
+		Map<LandCoverType, Double> convertibleAreas = new HashMap<LandCoverType, Double>();
+		Map<LandCoverType, Double> protectedAreas = new HashMap<LandCoverType, Double>();
+		for (LandCoverType lcType : LandCoverType.values()) {
+			convertibleAreas.put(lcType, landCoverAreas.get(lcType).getTotalArea(LandProtectionType.CONVERTIBLE));
+			protectedAreas.put(lcType, landCoverAreas.get(lcType).getTotalArea(LandProtectionType.PROTECTED));
+		}
+		return "LandUseItem: [landCoverAreas=" + convertibleAreas + ", protectedArea=" + protectedAreas + "]";
+	}
 }
\ No newline at end of file
diff --git a/src/ac/ed/lurg/landuse/LandUseReader.java b/src/ac/ed/lurg/landuse/LandUseReader.java
index bc8963300ff9d93b8d42c0c40ffd99df0566d548..48f278d16a4cdf7a994e59ca22f3c7492c587224 100644
--- a/src/ac/ed/lurg/landuse/LandUseReader.java
+++ b/src/ac/ed/lurg/landuse/LandUseReader.java
@@ -1,41 +1,42 @@
-package ac.ed.lurg.landuse;
-
-import java.util.Map;
-
-import ac.ed.lurg.types.CropType;
-import ac.ed.lurg.types.LandCoverType;
-import ac.sac.raster.AbstractTabularRasterReader;
-import ac.sac.raster.RasterKey;
-import ac.sac.raster.RasterSet;
-
-// This class is currently not use as using serialization instead, but could be handy at some stage
-public class LandUseReader extends AbstractTabularRasterReader<LandUseItem> {	
-
-	private static final int MIN_COLS = 20;
-
-	public LandUseReader(RasterSet<LandUseItem> landCover) {
-		super(",", MIN_COLS, landCover);
-	}
-
-	@Override
-	protected void setData(RasterKey key, LandUseItem luData, Map<String, Double> rowValues) {
-		
-		for (LandCoverType cover : LandCoverType.values()) {
-			luData.setConvertibleLandCoverArea(cover, getValueForCol(rowValues, cover.getName()));
-		}
-		
-		luData.setProtectedFraction(getValueForCol(rowValues, "protected") / luData.getTotalLandCoverArea());
-
-		for (CropType crop : CropType.getAllItems()) {
-  			double cropFract = getValueForCol(rowValues, crop.getGamsName() + "_A");
-			if (cropFract > 0.0 | CropType.PASTURE.equals(crop)) {  // pasture isn't a crop so cropFract will always be zero for it, but still want to set intensities
-				luData.setCropFraction(crop, cropFract);
-				double fertI = getValueForCol(rowValues, crop.getGamsName() + "_FI");
-				double irrigI = getValueForCol(rowValues, crop.getGamsName() + "_II");
-				double otherI = getValueForCol(rowValues, crop.getGamsName() + "_OI");
-				Intensity intensity = new Intensity(fertI, irrigI, otherI);
-				luData.setIntensity(crop, intensity);
-			}
-		}
-	}
-}
+package ac.ed.lurg.landuse;
+
+import java.util.Map;
+
+import ac.ed.lurg.types.CropType;
+import ac.ed.lurg.types.LandCoverType;
+import ac.ed.lurg.types.LandProtectionType;
+import ac.sac.raster.AbstractTabularRasterReader;
+import ac.sac.raster.RasterKey;
+import ac.sac.raster.RasterSet;
+
+// This class is currently not use as using serialization instead, but could be handy at some stage
+public class LandUseReader extends AbstractTabularRasterReader<LandUseItem> {	
+
+	private static final int MIN_COLS = 20;
+
+	public LandUseReader(RasterSet<LandUseItem> landCover) {
+		super(",", MIN_COLS, landCover);
+	}
+
+	@Override
+	protected void setData(RasterKey key, LandUseItem luData, Map<String, Double> rowValues) {
+		
+		for (LandCoverType cover : LandCoverType.values()) {
+			luData.setLandCoverArea(cover, LandProtectionType.CONVERTIBLE, getValueForCol(rowValues, cover.getName()));
+		}
+		
+		luData.setProtectedFraction(getValueForCol(rowValues, "protected") / luData.getTotalLandCoverArea());
+
+		for (CropType crop : CropType.getAllItems()) {
+  			double cropFract = getValueForCol(rowValues, crop.getGamsName() + "_A");
+			if (cropFract > 0.0 | CropType.PASTURE.equals(crop)) {  // pasture isn't a crop so cropFract will always be zero for it, but still want to set intensities
+				luData.setCropFraction(crop, cropFract);
+				double fertI = getValueForCol(rowValues, crop.getGamsName() + "_FI");
+				double irrigI = getValueForCol(rowValues, crop.getGamsName() + "_II");
+				double otherI = getValueForCol(rowValues, crop.getGamsName() + "_OI");
+				Intensity intensity = new Intensity(fertI, irrigI, otherI);
+				luData.setIntensity(crop, intensity);
+			}
+		}
+	}
+}
diff --git a/src/ac/ed/lurg/landuse/LandUseSerializer.java b/src/ac/ed/lurg/landuse/LandUseSerializer.java
deleted file mode 100644
index 7f2d3091ba96d27954e67cc9994a6108a01c4b53..0000000000000000000000000000000000000000
--- a/src/ac/ed/lurg/landuse/LandUseSerializer.java
+++ /dev/null
@@ -1,270 +0,0 @@
-package ac.ed.lurg.landuse;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.UnsupportedEncodingException;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.xml.stream.FactoryConfigurationError;
-import javax.xml.stream.XMLOutputFactory;
-import javax.xml.stream.XMLStreamException;
-import javax.xml.stream.XMLStreamWriter;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.xml.sax.SAXException;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-
-import ac.ed.lurg.ModelConfig;
-import ac.ed.lurg.types.CropType;
-import ac.ed.lurg.types.LandCoverType;
-import ac.ed.lurg.types.LandProtectionType;
-import ac.ed.lurg.utils.LogWriter;
-import ac.sac.raster.RasterKey;
-import ac.sac.raster.RasterSet;
-
-public class LandUseSerializer {
-	
-	XMLStreamWriter xmlWriter;
-	
-	public void LandUseSerialiser() {}
-
-	public void serialiseLandUse(RasterSet<LandUseItem> luRaster) {
-		
-		try {
-
-		OutputStream outStream = new FileOutputStream(new File(ModelConfig.SERIALIZED_LAND_COVER_FILE));
-		
-		xmlWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(
-				new OutputStreamWriter(outStream, "utf-8"));
-		
-		xmlWriter.writeStartDocument("UTF-8", "1.0");
-		xmlWriter.writeStartElement("LandUseItems");
-
-		int counter = 1;
-		for (Map.Entry<RasterKey, LandUseItem> entry : luRaster.entrySet()) {
-			RasterKey key = entry.getKey();
-			LandUseItem luItem = entry.getValue();
-			
-			xmlWriter.writeStartElement("LandUseItem");
-			xmlWriter.writeAttribute("id", Integer.toString(counter));
-			writeElement("col", key.getCol());
-			writeElement("row", key.getRow());
-			
-			//writeIntensityData(luItem.getIntensityMap());
-			//writeCropData(luItem.getCropFractionMap());
-			writeLandCoverData(luItem.getLandCoverTiles());
-			//writeElement("protectedFraction", luItem.getProtectedFraction());
-			
-			xmlWriter.writeEndElement(); // LandUseItem
-			xmlWriter.flush();
-			counter++;
-			//if (counter > 1) break;
-		}
-
-		xmlWriter.writeEndElement(); // LandUseItems
-		xmlWriter.writeEndDocument();
-		
-		xmlWriter.flush();
-		xmlWriter.close();
-		
-		} catch (FileNotFoundException e) {
-			LogWriter.print(e);
-		} catch (XMLStreamException e) {
-			LogWriter.print(e);
-		} catch (UnsupportedEncodingException e) {
-			LogWriter.print(e);
-		} catch (FactoryConfigurationError e) {
-			LogWriter.print(e.getException());
-		}
-	}
-	
-	private void writeIntensityData(Map<CropType, Intensity> intensityMap) {
-		try {
-			xmlWriter.writeStartElement("intensity");
-			for (Map.Entry<CropType, Intensity> entry : intensityMap.entrySet()) {
-				CropType crop = entry.getKey();
-				Intensity inten = entry.getValue();
-				if (inten == null) {
-					continue;
-				}
-				xmlWriter.writeStartElement(crop.getGamsName());
-				writeElement("fertiliserIntensity", inten.getFertiliserIntensity());
-				writeElement("irrigationIntensity", inten.getIrrigationIntensity());
-				writeElement("otherIntensity", inten.getOtherIntensity());
-				writeElement("yield", inten.getYield());
-				writeElement("unitEnergy", inten.getUnitEnergy());
-				writeElement("maxIrrigRate", inten.getMaxIrrigRate());
-				xmlWriter.writeEndElement();
-			}
-			xmlWriter.writeEndElement();
-
-
-		} catch (XMLStreamException e) {
-			LogWriter.print(e);
-		} 
-
-	}
-	
-	private void writeCropData(Map<CropType, Double> cropFractMap) {
-		try {
-			xmlWriter.writeStartElement("cropFractions");
-			for (Map.Entry<CropType, Double> entry : cropFractMap.entrySet()) {
-				CropType crop = entry.getKey();
-				Double fract = entry.getValue();
-				if (fract == null) {
-					continue;
-				}
-				writeElement(crop.getGamsName(), fract.doubleValue());
-
-			}
-			xmlWriter.writeEndElement();
-
-		} catch (XMLStreamException e) {
-			LogWriter.print(e);
-		} 
-
-	}
-	
-	private void writeLandCoverData(Map<LandCoverType, LandCoverTile> lcData) {
-		try {
-			xmlWriter.writeStartElement("landCover");
-			for (Map.Entry<LandCoverType, LandCoverTile> entry : lcData.entrySet()) {
-				LandCoverType lcType = entry.getKey();
-				LandCoverTile tiles = entry.getValue();
-				if (tiles == null) {
-					continue;
-				}
-				xmlWriter.writeStartElement(lcType.getName());
-				
-				xmlWriter.writeStartElement("convertible");
-				for (int a = 0; a <= LandCoverTile.getMaxAgeBin(); a++) {
-					double area = tiles.getArea(LandProtectionType.CONVERTIBLE, a);
-					if (area == 0) 
-						continue;
-					writeElement("X"+Integer.toString(a), area);
-				}
-				xmlWriter.writeEndElement();
-				
-				xmlWriter.writeStartElement("protected");
-				for (int a = 0; a <= LandCoverTile.getMaxAgeBin(); a++) {
-					double area = tiles.getArea(LandProtectionType.PROTECTED, a);
-					if (area == 0) 
-						continue;
-					writeElement("X"+Integer.toString(a), area);
-				}
-				xmlWriter.writeEndElement();
-				
-				writeElement("unavailable", tiles.getTotalArea(LandProtectionType.UNAVAILABLE));
-				
-				xmlWriter.writeEndElement();
-			}
-			xmlWriter.writeEndElement();
-
-
-		} catch (XMLStreamException e) {
-			LogWriter.print(e);
-		} 
-
-	}
-
-	private void writeElement(String element, double value) {
-		try {
-			xmlWriter.writeStartElement(element);
-			xmlWriter.writeCharacters(Double.toString(value));
-			xmlWriter.writeEndElement();
-		} catch (XMLStreamException e) {
-			LogWriter.print(e);
-		}
-	}
-	
-	private void writeElement(String element, int value) {
-		try {
-			xmlWriter.writeStartElement(element);
-			xmlWriter.writeCharacters(Integer.toString(value));
-			xmlWriter.writeEndElement();
-		} catch (XMLStreamException e) {
-			LogWriter.print(e);
-		}
-	}
-	
-	public void deserializeLandUse(RasterSet<LandUseItem> luRaster) {
-		DocumentBuilderFactory fact = DocumentBuilderFactory.newInstance();
-		DocumentBuilder builder;
-
-		Document doc;
-		try {
-			builder = fact.newDocumentBuilder();
-			doc = builder.parse(new File(ModelConfig.SERIALIZED_LAND_COVER_FILE));
-			doc.getDocumentElement().normalize();
-			
-			Element root = doc.getDocumentElement();
-			NodeList nodeLParent = doc.getElementsByTagName("LandUseItems");
-			NodeList nodeL = nodeLParent.item(0).getChildNodes();
-			
-			for (int i = 0; i < nodeL.getLength(); i++) {
-				Node node = nodeL.item(i);
-				Map<LandCoverType, LandCoverTile> landCoverAreas = new HashMap<LandCoverType, LandCoverTile>();
-				for (LandCoverType lcType: LandCoverType.values()) {
-					landCoverAreas.put(lcType, new LandCoverTile());
-				}
-				if (node.getNodeType() == Node.ELEMENT_NODE) {
-					Element itemElement = (Element) node;
-					int col = Integer.parseInt(itemElement.getElementsByTagName("col").item(0).getTextContent()); // TODO store as attribute
-					int row = Integer.parseInt(itemElement.getElementsByTagName("row").item(0).getTextContent());
-					
-					NodeList lcNodeListParent = itemElement.getElementsByTagName("landCover");
-					NodeList lcNodeList = lcNodeListParent.item(0).getChildNodes();
-					Element lcNodeElement = (Element) lcNodeList;
-					
-					
-					for (int j = 0; j < lcNodeList.getLength(); j++) {
-						Node lcNode = lcNodeList.item(j);
-						
-						if (node.getNodeType() == Node.ELEMENT_NODE) {
-							LandCoverType lcType = LandCoverType.getForName(lcNode.getNodeName());
-							Element lcElement = (Element) lcNode;
-							NodeList convElements = lcElement.getElementsByTagName("convertible").item(0).getChildNodes();
-							NodeList protElements = lcElement.getElementsByTagName("protected").item(0).getChildNodes();
-							double unavArea = Double.parseDouble(lcElement.getElementsByTagName("unavailable").item(0).getTextContent());
-							landCoverAreas.get(lcType).setArea(LandProtectionType.UNAVAILABLE, 0, unavArea);
-							
-							for (int k = 0; k < convElements.getLength(); k++) {
-								Node areaNode = convElements.item(k);
-								int age = Integer.parseInt(areaNode.getNodeName().replace("X", ""));
-								//double area = Double.parseDouble(areaNode.getNodeValue());
-								double area = Double.parseDouble(areaNode.getChildNodes().item(0).getNodeValue());
-								landCoverAreas.get(lcType).setArea(LandProtectionType.CONVERTIBLE, age, area);
-							}
-							
-							for (int k = 0; k < protElements.getLength(); k++) {
-								Node areaNode = protElements.item(k);
-								int age = Integer.parseInt(areaNode.getNodeName().replace("X", ""));
-								//double area = Double.parseDouble(areaNode.getNodeValue());
-								double area = Double.parseDouble(areaNode.getChildNodes().item(0).getNodeValue());
-								landCoverAreas.get(lcType).setArea(LandProtectionType.PROTECTED, age, area);
-							}
-						}
-					}	
-					luRaster.get(new RasterKey(col, row)).overwriteLandCoverAreas(landCoverAreas);
-				}
-				
-			}
-			
-		} catch (SAXException | IOException | ParserConfigurationException e) {
-			// TODO Auto-generated catch block
-			LogWriter.print(e);;
-		}
-
-	}
-}
diff --git a/src/ac/ed/lurg/landuse/WoodUsageData.java b/src/ac/ed/lurg/landuse/WoodUsageData.java
index 2a2d29f86c48b5f3910998b3efcf7fe2c3a98276..d650ef5d85de31830f3d017d73e287c89a9e4d91 100644
--- a/src/ac/ed/lurg/landuse/WoodUsageData.java
+++ b/src/ac/ed/lurg/landuse/WoodUsageData.java
@@ -1,24 +1,30 @@
-package ac.ed.lurg.landuse;
-
-import java.io.Serializable;
-
-public class WoodUsageData implements Serializable {
-
-	private static final long serialVersionUID = -3329080279189782763L;
-	
-	private double harvest;
-	private double netImport;
-	
-	public WoodUsageData(double harvest, double netImport) {
-		this.harvest = harvest;
-		this.netImport = netImport;
-	}
-
-	public double getHarvest() {
-		return harvest;
-	}
-
-	public double getNetImport() {
-		return netImport;
-	}	
-}
+package ac.ed.lurg.landuse;
+
+import java.io.Serializable;
+
+public class WoodUsageData implements Serializable {
+
+	private static final long serialVersionUID = -3329080279189782763L;
+	
+	private double harvest;
+	private double netImport;
+	private double lucHarvest;
+	
+	public WoodUsageData(double harvest, double netImport, double lucHarvest) {
+		this.harvest = harvest;
+		this.netImport = netImport;
+		this.lucHarvest = lucHarvest;
+	}
+
+	public double getHarvest() {
+		return harvest;
+	}
+
+	public double getNetImport() {
+		return netImport;
+	}
+
+	public double getLucHarvest() {
+		return lucHarvest;
+	}	
+}
diff --git a/src/ac/ed/lurg/landuse/WoodUsageReader.java b/src/ac/ed/lurg/landuse/WoodUsageReader.java
index e161e122a393ee3f8abf46c12899be8140567ffa..0a3e50d751cac8816e87ddfbebb641fcd5a211d5 100644
--- a/src/ac/ed/lurg/landuse/WoodUsageReader.java
+++ b/src/ac/ed/lurg/landuse/WoodUsageReader.java
@@ -1,96 +1,96 @@
-package ac.ed.lurg.landuse;
-
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-
-import ac.ed.lurg.country.CompositeCountryManager;
-import ac.ed.lurg.country.CountryManager;
-import ac.ed.lurg.country.SingleCountry;
-import ac.ed.lurg.ModelConfig;
-import ac.ed.lurg.country.CompositeCountry;
-import ac.ed.lurg.types.WoodType;
-import ac.ed.lurg.utils.LazyHashMap;
-import ac.ed.lurg.utils.LogWriter;
-
-public class WoodUsageReader {
-	private static final int COUNTRY_COL = 0;
-	private static final int WOOD_TYPE_COL = 1;
-	private static final int HARVEST_COL = 2;
-	private static final int NET_IMPORT_COL = 3;
-	
-	private CompositeCountryManager compositeCountryManager;
-	
-	public WoodUsageReader(CompositeCountryManager compositeCountryManager) {
-		this.compositeCountryManager = compositeCountryManager;
-	}
-	
-	public Map<CompositeCountry, Map<WoodType, WoodUsageData>> getWoodUsageData() {
-		
-		LazyHashMap<CompositeCountry, Map<WoodType, WoodUsageData>> usageMap = new LazyHashMap<CompositeCountry, Map<WoodType, WoodUsageData>>() {
-			private static final long serialVersionUID = -3848653213145539313L;
-
-			protected Map<WoodType, WoodUsageData> createValue() { 
-				Map<WoodType, WoodUsageData> countryData = new HashMap<WoodType, WoodUsageData>();
-				return countryData;
-			}
-		};
-		
-		String filename = ModelConfig.WOOD_NET_IMPORTS_FILE;
-		try {
-			BufferedReader reader = new BufferedReader(new FileReader(filename));
-			String line, countryName, woodTypeName;
-			double harvest, netImport;
-			reader.readLine(); // read header
-			
-			while ((line = reader.readLine()) != null) {
-				String[] tokens = line.split(",");
-				
-				if (tokens.length < 4)
-					LogWriter.printlnError("Too few columns in file: " + filename + ", line: " + line);
-				
-				countryName = tokens[COUNTRY_COL];
-				woodTypeName = tokens[WOOD_TYPE_COL];
-				harvest = Double.parseDouble(tokens[HARVEST_COL]) * ModelConfig.WOOD_BIOMASS_CONVERSION_FACTOR; // m3 to MtC-eq
-				netImport = Double.parseDouble(tokens[NET_IMPORT_COL]) * ModelConfig.WOOD_BIOMASS_CONVERSION_FACTOR; // m3 to MtC-eq
-				
-				SingleCountry country = CountryManager.getForName(countryName);
-				WoodType woodType = WoodType.getForName(woodTypeName);
-				
-				if (country == null) {
-					// LogWriter.printlnWarning("WoodUsageReader can't find single country: " + countryName);
-					continue;
-				}
-				
-				CompositeCountry cc = compositeCountryManager.getForSingleCountry(country);
-				
-				if (cc == null) {
-					// LogWriter.printlnWarning("WoodUsageReader can't find composite country for: " + country.getCountryName());
-					continue;
-				}
-				
-				Map<WoodType, WoodUsageData> countryData = usageMap.lazyGet(cc);
-				WoodUsageData oldData = countryData.get(woodType);
-				
-				// aggregate if multiple countries for cc
-				if (oldData != null) {
-					netImport += oldData.getNetImport();
-					harvest += oldData.getHarvest();
-				}
-				
-				WoodUsageData newData = new WoodUsageData(harvest, netImport);
-				countryData.put(woodType, newData);
-			}
-			reader.close();
-			
-		} catch (IOException e) {
-			LogWriter.printlnError("Failed in reading wood usage data");
-			LogWriter.print(e);
-		}
-		LogWriter.println("Processed " + filename + ", create " + usageMap.size() + " country wood usage map values");
-		
-		return usageMap;
-	}
-}
+package ac.ed.lurg.landuse;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import ac.ed.lurg.country.CompositeCountryManager;
+import ac.ed.lurg.country.CountryManager;
+import ac.ed.lurg.country.SingleCountry;
+import ac.ed.lurg.ModelConfig;
+import ac.ed.lurg.country.CompositeCountry;
+import ac.ed.lurg.types.WoodType;
+import ac.ed.lurg.utils.LazyHashMap;
+import ac.ed.lurg.utils.LogWriter;
+
+public class WoodUsageReader {
+	private static final int COUNTRY_COL = 0;
+	private static final int WOOD_TYPE_COL = 1;
+	private static final int HARVEST_COL = 2;
+	private static final int NET_IMPORT_COL = 3;
+	
+	private CompositeCountryManager compositeCountryManager;
+	
+	public WoodUsageReader(CompositeCountryManager compositeCountryManager) {
+		this.compositeCountryManager = compositeCountryManager;
+	}
+	
+	public Map<CompositeCountry, Map<WoodType, WoodUsageData>> getWoodUsageData() {
+		
+		LazyHashMap<CompositeCountry, Map<WoodType, WoodUsageData>> usageMap = new LazyHashMap<CompositeCountry, Map<WoodType, WoodUsageData>>() {
+			private static final long serialVersionUID = -3848653213145539313L;
+
+			protected Map<WoodType, WoodUsageData> createValue() { 
+				Map<WoodType, WoodUsageData> countryData = new HashMap<WoodType, WoodUsageData>();
+				return countryData;
+			}
+		};
+		
+		String filename = ModelConfig.WOOD_NET_IMPORTS_FILE;
+		try {
+			BufferedReader reader = new BufferedReader(new FileReader(filename));
+			String line, countryName, woodTypeName;
+			double harvest, netImport;
+			reader.readLine(); // read header
+			
+			while ((line = reader.readLine()) != null) {
+				String[] tokens = line.split(",");
+				
+				if (tokens.length < 4)
+					LogWriter.printlnError("Too few columns in file: " + filename + ", line: " + line);
+				
+				countryName = tokens[COUNTRY_COL];
+				woodTypeName = tokens[WOOD_TYPE_COL];
+				harvest = Double.parseDouble(tokens[HARVEST_COL]) * ModelConfig.WOOD_BIOMASS_CONVERSION_FACTOR; // m3 to MtC-eq
+				netImport = Double.parseDouble(tokens[NET_IMPORT_COL]) * ModelConfig.WOOD_BIOMASS_CONVERSION_FACTOR; // m3 to MtC-eq
+				
+				SingleCountry country = CountryManager.getForName(countryName);
+				WoodType woodType = WoodType.getForName(woodTypeName);
+				
+				if (country == null) {
+					// LogWriter.printlnWarning("WoodUsageReader can't find single country: " + countryName);
+					continue;
+				}
+				
+				CompositeCountry cc = compositeCountryManager.getForSingleCountry(country);
+				
+				if (cc == null) {
+					// LogWriter.printlnWarning("WoodUsageReader can't find composite country for: " + country.getCountryName());
+					continue;
+				}
+				
+				Map<WoodType, WoodUsageData> countryData = usageMap.lazyGet(cc);
+				WoodUsageData oldData = countryData.get(woodType);
+				
+				// aggregate if multiple countries for cc
+				if (oldData != null) {
+					netImport += oldData.getNetImport();
+					harvest += oldData.getHarvest();
+				}
+				
+				WoodUsageData newData = new WoodUsageData(harvest, netImport, 0);
+				countryData.put(woodType, newData);
+			}
+			reader.close();
+			
+		} catch (IOException e) {
+			LogWriter.printlnError("Failed in reading wood usage data");
+			LogWriter.print(e);
+		}
+		LogWriter.println("Processed " + filename + ", create " + usageMap.size() + " country wood usage map values");
+		
+		return usageMap;
+	}
+}
diff --git a/src/ac/ed/lurg/output/LandUseOutputer.java b/src/ac/ed/lurg/output/LandUseOutputer.java
index 82eed7ea30d5d5841e87e1ca8bc37a94529a406e..ab42c6fc325877d4c40071da70446b15a30a04bc 100644
--- a/src/ac/ed/lurg/output/LandUseOutputer.java
+++ b/src/ac/ed/lurg/output/LandUseOutputer.java
@@ -1,99 +1,100 @@
-package ac.ed.lurg.output;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.Map.Entry;
-
-import ac.ed.lurg.landuse.Intensity;
-import ac.ed.lurg.landuse.LandUseItem;
-import ac.ed.lurg.types.CropType;
-import ac.ed.lurg.types.LandCoverType;
-import ac.ed.lurg.utils.LogWriter;
-import ac.sac.raster.RasterKey;
-import ac.sac.raster.RasterSet;
-
-public class LandUseOutputer extends AbstractLandUseOutputer {
-
-	public LandUseOutputer(int year, RasterSet<LandUseItem> landUseRaster) {
-		super(year, landUseRaster);
-	}
-
-	@Override
-	public void writeOutput() {
-		File outputDir = getOutputDir(year);
-		BufferedWriter fertWriter = null;
-		
-		try {
-			String landCoverFileName = outputDir.getPath() + File.separator + "LandUse.txt";
-			fertWriter = new BufferedWriter(new FileWriter(landCoverFileName, false));
-		
-			StringBuffer sbHeader = new StringBuffer("Lon Lat area suitable protected pa_fraction");
-			
-			for (LandCoverType cover : LandCoverType.values()) {
-				sbHeader.append(" " + cover.getName());
-			}
-			
-			for (CropType crop : CropType.getNonMeatTypes()) {
-				String cropString = crop.getGamsName();
-				sbHeader.append(" " + cropString + "_A " + cropString + "_FI " + cropString + "_FQ " + 
-							cropString + "_II " + cropString + "_IQ " + cropString + "_OI " + cropString + "_Y");
-			}
-			fertWriter.write(sbHeader.toString());
-			fertWriter.newLine();
-
-			for (Entry<RasterKey, LandUseItem> entry : landUseRaster.entrySet()) {
-				RasterKey key = entry.getKey();
-				LandUseItem item = entry.getValue();
-				
-				if (item == null)
-					continue;
-				
-				double lon = landUseRaster.getXCoordin(key);
-				double lat = landUseRaster.getYCoordin(key);
-				
-			
-				StringBuffer sbData = new StringBuffer(String.format("%.2f %.2f", lon, lat));
-
-				sbData.append(String.format(" %.8f", item.getTotalLandCoverArea()));
-				sbData.append(String.format(" %.8f", item.getSuitableArea()));
-				sbData.append(String.format(" %.8f", item.getTotalProtectedArea()));
-				sbData.append(String.format(" %.8f", item.getTotalProtectedArea()/item.getTotalLandCoverArea()));
-				
-				for (LandCoverType cover : LandCoverType.values()) {
-					sbData.append(String.format(" %.8f", item.getLandCoverArea(cover)));
-				}
-
-				for (CropType crop : CropType.getNonMeatTypes()) {
-					double cropFract = item.getCropFraction(crop);
-					Intensity intensity = item.getIntensity(crop);
-					double fertI = intensity==null ? 0.0 : intensity.getFertiliserIntensity();
-					double fertQ = intensity==null ? 0.0 : intensity.getFertiliserAmount();
-					double irrigI = intensity==null ? 0.0 : intensity.getIrrigationIntensity();
-					double irrigQ = intensity==null ? 0.0 : intensity.getIrrigationRate();
-					double otherI = intensity==null ? 0.0 : intensity.getOtherIntensity();
-					double yield = intensity==null ? 0.0 : intensity.getYield();
-					sbData.append(String.format(" %.8f %.8f %.8f %.8f %.8f %.8f %.8f", cropFract, fertI, fertQ, irrigI, irrigQ, otherI, yield));
-				}
-				fertWriter.write(sbData.toString());
-				fertWriter.newLine();
-			}
-		}
-		catch (IOException e) {
-			LogWriter.print(e);
-		}
-		finally {
-			if (fertWriter != null) {
-				try {
-					fertWriter.close();
-				} 
-				catch (IOException e) {
-					LogWriter.print(e);
-				}
-			}
-		}
-		
-	}
-
-}
+package ac.ed.lurg.output;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Map.Entry;
+
+import ac.ed.lurg.landuse.Intensity;
+import ac.ed.lurg.landuse.LandUseItem;
+import ac.ed.lurg.types.CropType;
+import ac.ed.lurg.types.LandCoverType;
+import ac.ed.lurg.types.LandProtectionType;
+import ac.ed.lurg.utils.LogWriter;
+import ac.sac.raster.RasterKey;
+import ac.sac.raster.RasterSet;
+
+public class LandUseOutputer extends AbstractLandUseOutputer {
+
+	public LandUseOutputer(int year, RasterSet<LandUseItem> landUseRaster) {
+		super(year, landUseRaster);
+	}
+
+	@Override
+	public void writeOutput() {
+		File outputDir = getOutputDir(year);
+		BufferedWriter fertWriter = null;
+		
+		try {
+			String landCoverFileName = outputDir.getPath() + File.separator + "LandUse.txt";
+			fertWriter = new BufferedWriter(new FileWriter(landCoverFileName, false));
+		
+			StringBuffer sbHeader = new StringBuffer("Lon Lat area suitable protected pa_fraction");
+			
+			for (LandCoverType cover : LandCoverType.values()) {
+				sbHeader.append(" " + cover.getName());
+			}
+			
+			for (CropType crop : CropType.getNonMeatTypes()) {
+				String cropString = crop.getGamsName();
+				sbHeader.append(" " + cropString + "_A " + cropString + "_FI " + cropString + "_FQ " + 
+							cropString + "_II " + cropString + "_IQ " + cropString + "_OI " + cropString + "_Y");
+			}
+			fertWriter.write(sbHeader.toString());
+			fertWriter.newLine();
+
+			for (Entry<RasterKey, LandUseItem> entry : landUseRaster.entrySet()) {
+				RasterKey key = entry.getKey();
+				LandUseItem item = entry.getValue();
+				
+				if (item == null)
+					continue;
+				
+				double lon = landUseRaster.getXCoordin(key);
+				double lat = landUseRaster.getYCoordin(key);
+				
+			
+				StringBuffer sbData = new StringBuffer(String.format("%.2f %.2f", lon, lat));
+
+				sbData.append(String.format(" %.8f", item.getTotalLandCoverArea()));
+				sbData.append(String.format(" %.8f", item.getSuitableArea()));
+				sbData.append(String.format(" %.8f", item.getTotalLandCoverArea(LandProtectionType.PROTECTED)));
+				sbData.append(String.format(" %.8f", item.getTotalLandCoverArea(LandProtectionType.PROTECTED)/item.getTotalLandCoverArea()));
+				
+				for (LandCoverType cover : LandCoverType.values()) {
+					sbData.append(String.format(" %.8f", item.getLandCoverArea(cover)));
+				}
+
+				for (CropType crop : CropType.getNonMeatTypes()) {
+					double cropFract = item.getCropFraction(crop);
+					Intensity intensity = item.getIntensity(crop);
+					double fertI = intensity==null ? 0.0 : intensity.getFertiliserIntensity();
+					double fertQ = intensity==null ? 0.0 : intensity.getFertiliserAmount();
+					double irrigI = intensity==null ? 0.0 : intensity.getIrrigationIntensity();
+					double irrigQ = intensity==null ? 0.0 : intensity.getIrrigationRate();
+					double otherI = intensity==null ? 0.0 : intensity.getOtherIntensity();
+					double yield = intensity==null ? 0.0 : intensity.getYield();
+					sbData.append(String.format(" %.8f %.8f %.8f %.8f %.8f %.8f %.8f", cropFract, fertI, fertQ, irrigI, irrigQ, otherI, yield));
+				}
+				fertWriter.write(sbData.toString());
+				fertWriter.newLine();
+			}
+		}
+		catch (IOException e) {
+			LogWriter.print(e);
+		}
+		finally {
+			if (fertWriter != null) {
+				try {
+					fertWriter.close();
+				} 
+				catch (IOException e) {
+					LogWriter.print(e);
+				}
+			}
+		}
+		
+	}
+
+}
diff --git a/src/ac/ed/lurg/types/LandCoverType.java b/src/ac/ed/lurg/types/LandCoverType.java
index 7398c8a2c2f658ed7f264b70ea1c57f434cf00a0..d9aac67874a670c815ea53063c0039a2ef01a919 100644
--- a/src/ac/ed/lurg/types/LandCoverType.java
+++ b/src/ac/ed/lurg/types/LandCoverType.java
@@ -1,132 +1,132 @@
-package ac.ed.lurg.types;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-
-import ac.ed.lurg.utils.LogWriter;
-
-public enum LandCoverType {
-	
-	TIMBER_FOREST("timberForest", false, true, true, true),
-	CARBON_FOREST("carbonForest", false, true, true, true),
-	NATURAL ("natural", true, true, true, false),
-	CROPLAND ("cropland", false, true, false, false),
-	PASTURE ("pasture", false, true, false, false),
-	BARREN ("barren", false, false, false, false),
-	URBAN("urban", false, false, false, false);
-
-	private String name;
-	private boolean isProtectable;
-	private boolean isConvertible;
-	private boolean isNatural;
-	private boolean isManagedForest;
-	
-	LandCoverType(String name, boolean isProtectable, boolean isConvertible, boolean isNatural, boolean isManagedForest) {
-		this.name = name;
-		this.isProtectable = isProtectable;
-		this.isConvertible = isConvertible;
-		this.isNatural = isNatural;
-		this.isManagedForest = isManagedForest;
-	}
-	
-	public String getName() {
-		return name;
-	}
-	
-	public boolean isProtectable() {
-		return isProtectable;
-	}
-	
-	public boolean isConvertible() {
-		return isConvertible;
-	}
-
-	public boolean isNatural() {
-		return isNatural;
-	}
-	
-	public boolean isManagedForest() {
-		return isManagedForest;
-	}
-	
-	private static final Map<String, LandCoverType> nameCache = new HashMap<String, LandCoverType>();
-    static {
-        for (LandCoverType c : values()) {
-        	nameCache.put(c.getName(), c);
-
-       }
-    }
-
-	public static LandCoverType getForName(String name) {
-		LandCoverType type = nameCache.get(name);
-		
-		if (type == null)
-			LogWriter.printlnError("Can't find LandCoverType for " + name);
-		
-		return type;
-	}
-	
-	
-	public static Collection<LandCoverType> getConvertibleTypes() {
-
-		Collection<LandCoverType> convertibleTypes = new HashSet<LandCoverType>();
-
-		for (LandCoverType c : values())
-			if (c.isConvertible)
-				convertibleTypes.add(c);
-
-		return convertibleTypes;
-
-	}
-	
-	public static Collection<LandCoverType> getProtectibleTypes() {
-
-		Collection<LandCoverType> protectibleTypes = new HashSet<LandCoverType>();
-
-		for (LandCoverType c : values())
-			if (c.isProtectable)
-				protectibleTypes.add(c);
-
-		return protectibleTypes;
-
-	}
-	
-	public static Collection<LandCoverType> getManagedForestTypes() {
-
-		Collection<LandCoverType> managedForestTypes = new HashSet<LandCoverType>();
-
-		for (LandCoverType c : values())
-			if (c.isManagedForest)
-				managedForestTypes.add(c);
-
-		return managedForestTypes;
-
-	}
-	
-	public static Collection<LandCoverType> getNaturalTypes() {
-
-		Collection<LandCoverType> naturalTypes = new HashSet<LandCoverType>();
-
-		for (LandCoverType c : values())
-			if (c.isNatural)
-				naturalTypes.add(c);
-
-		return naturalTypes;
-
-	}
-	
-	public static Collection<LandCoverType> getAgriculturalTypes() {
-
-		Collection<LandCoverType> agriTypes = new HashSet<LandCoverType>();
-
-		for (LandCoverType c : values())
-			if (!c.isNatural && c.isConvertible)
-				agriTypes.add(c);
-
-		return agriTypes;
-
-	}
-}
-
+package ac.ed.lurg.types;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+import ac.ed.lurg.utils.LogWriter;
+
+public enum LandCoverType {
+	
+	TIMBER_FOREST("timberForest", false, true, true, true),
+	CARBON_FOREST("carbonForest", false, true, true, true),
+	NATURAL ("natural", true, true, true, false),
+	CROPLAND ("cropland", false, true, false, false),
+	PASTURE ("pasture", false, true, false, false),
+	BARREN ("barren", false, false, false, false),
+	URBAN("urban", true, false, false, false);
+
+	private String name;
+	private boolean isProtectable;
+	private boolean isConvertible;
+	private boolean isNatural;
+	private boolean isManagedForest;
+	
+	LandCoverType(String name, boolean isProtectable, boolean isConvertible, boolean isNatural, boolean isManagedForest) {
+		this.name = name;
+		this.isProtectable = isProtectable;
+		this.isConvertible = isConvertible;
+		this.isNatural = isNatural;
+		this.isManagedForest = isManagedForest;
+	}
+	
+	public String getName() {
+		return name;
+	}
+	
+	public boolean isProtectable() {
+		return isProtectable;
+	}
+	
+	public boolean isConvertible() {
+		return isConvertible;
+	}
+
+	public boolean isNatural() {
+		return isNatural;
+	}
+	
+	public boolean isManagedForest() {
+		return isManagedForest;
+	}
+	
+	private static final Map<String, LandCoverType> nameCache = new HashMap<String, LandCoverType>();
+    static {
+        for (LandCoverType c : values()) {
+        	nameCache.put(c.getName(), c);
+
+       }
+    }
+
+	public static LandCoverType getForName(String name) {
+		LandCoverType type = nameCache.get(name);
+		
+		if (type == null)
+			LogWriter.printlnError("Can't find LandCoverType for " + name);
+		
+		return type;
+	}
+	
+	
+	public static Collection<LandCoverType> getConvertibleTypes() {
+
+		Collection<LandCoverType> convertibleTypes = new HashSet<LandCoverType>();
+
+		for (LandCoverType c : values())
+			if (c.isConvertible)
+				convertibleTypes.add(c);
+
+		return convertibleTypes;
+
+	}
+	
+	public static Collection<LandCoverType> getProtectibleTypes() {
+
+		Collection<LandCoverType> protectibleTypes = new HashSet<LandCoverType>();
+
+		for (LandCoverType c : values())
+			if (c.isProtectable)
+				protectibleTypes.add(c);
+
+		return protectibleTypes;
+
+	}
+	
+	public static Collection<LandCoverType> getManagedForestTypes() {
+
+		Collection<LandCoverType> managedForestTypes = new HashSet<LandCoverType>();
+
+		for (LandCoverType c : values())
+			if (c.isManagedForest)
+				managedForestTypes.add(c);
+
+		return managedForestTypes;
+
+	}
+	
+	public static Collection<LandCoverType> getNaturalTypes() {
+
+		Collection<LandCoverType> naturalTypes = new HashSet<LandCoverType>();
+
+		for (LandCoverType c : values())
+			if (c.isNatural)
+				naturalTypes.add(c);
+
+		return naturalTypes;
+
+	}
+	
+	public static Collection<LandCoverType> getAgriculturalTypes() {
+
+		Collection<LandCoverType> agriTypes = new HashSet<LandCoverType>();
+
+		for (LandCoverType c : values())
+			if (!c.isNatural && c.isConvertible)
+				agriTypes.add(c);
+
+		return agriTypes;
+
+	}
+}
+