happyjack27 / autoredistrict

Programmatically makes a fair congressional district map (prevents gerrymandering)
GNU General Public License v3.0
89 stars 15 forks source link

FULLY AUTOMATED REDISTRICTING SOFTWARE written in Java, open source.

How to Run

Requirements

Steps

  1. Open a command prompt.
  2. Change to the directory containing the jar.
  3. Execute java -jar autoredistrict.jar -Xmx4096M -Xms1024M

autoredistrict.jar is in the jar folder along with a script file for Windows and one for Linux/macOS. The -Xmx4096M -Xms1024M arguments tell java to reserve 1GB of memory, and allow additional allocation up to 4GB.

How to Build and Run from Source

Requirements

Steps

  1. Open a command prompt.
  2. Change to the directory that you cloned the repo to.
  3. Make a bin directory here.
  4. Execute mvn clean compile package exec:java

Running Tests

From a terminal

Simply execute mvn test.

From an IDE

You may need to add --add-opens java.base/java.util=ALL-UNNAMED as program arguments or VM arguments in your run configuration, in order to avoid getting java.lang.reflect.InaccessibleObjectExceptions in the console output.

Contributing

https://travis-ci.org/happyjack27/autoredistrict.svg?branch=master

TODO:

pct of area - total population - old - normalize to current population pct of area - total population - new

load into temporary geography file (each get its own min/max)

import data - different shapefile - "import incongruous data"

DONE:


a. A representative plan is one where racial groups and other communities of interest are able to elect representatives in proportion to their percentage of the voting age population.

  1. District boundaries shall conform to the existing geographic boundaries of a county, city, or city and county, and shall preserve identifiable communities of interest to the greatest extent possible. A redistricting plan shall provide for the most whole counties and the fewest county fragments possible, and the most whole cities and fewest city fragments possible. For the purposes of this section, communities of interest are defined by similarities in social, cultural, ethnic, and economic interest, school districts, and other formal relationships between municipalities.

  2. Information concerning party registration and historical election returns shall only be used once a plan has been drawn, and shall only be used to test the plan for compliance with the stated goals of this article.

DONE:

DONE: auto-annealing (instead of having to "drive" it)

TODO:

new ui:

=================
RESULT SCORING IN DEPTH

Initial loading

census and election data

Compactness (border length)

The total of length of all edges that have a different district on each side is accumulated.

class DistrictMap {
    double getEdgeLength() {
        double length = 0;
        for( ward b : wards) {
            int d1 = ward_districts[b.id];
            for( int i = 0; i < b.neighbor_lengths.length; i++) {
                int b2id = b.neighbors.get(i).id;
                int d2 = ward_districts[b2id];
                if( d1 != d2) {
                    length += b.neighbor_lengths[i];
                }
            }
        }
        return length;
    }
}

Connectedness

Each district is broken up into self-connected regions. The total population for each such region is accumulated from the poplygon populationos. The total population in the highest such region is subtracted from the total population in all regions combined, giving the toal population not in the largest region. This is the "disconnected population".

class DistrictMap {
    double disconnected_pops = 0;
    if( Settings.disconnected_population_weight > 0) {
        for(District district : districts) {
            //int count = district.getRegionCount(ward_districts);
            //System.out.println("region count: "+count);
            //disconnected_pops += count;
            disconnected_pops += district.getPopulation() - district.getRegionPopulation(district.getTopPopulationRegion(ward_districts));
        }
    }
}
class District {
    Vector<ward> getTopPopulationRegion(int[] ward_districts) {
        Vector<Vector<ward>> regions = getRegions(ward_districts);
        Vector<ward> high = null;
        double max_pop = 0;
        for( Vector<ward> region : regions) {
            double pop = getRegionPopulation(region);
            if( pop > max_pop || high == null) {
                max_pop = pop;
                high = region;
            }
        }
        return high;
    }
    Vector<Vector<ward>> getRegions(int[] ward_districts) {
        Hashtable<Integer,Vector<ward>> region_hash = new Hashtable<Integer,Vector<ward>>();
        Vector<Vector<ward>> regions = new Vector<Vector<ward>>();
        for( ward ward : wards) {
            if( region_hash.get(ward.id) != null)
                continue;
            Vector<ward> region = new Vector<ward>();
            regions.add(region);
            addAllConnected(ward,region,region_hash,ward_districts);
        }
        return regions;
    }
    //recursively insert connected wards.
    void addAllConnected( ward ward, Vector<ward> region,  Hashtable<Integer,Vector<ward>> region_hash, int[] ward_districts) {
        if( region_hash.get(ward.id) != null)
            return;
        region.add(ward);
        region_hash.put(ward.id,region);
        for( ward other_ward : ward.neighbors) {
            if( ward_districts[other_ward.id] == ward_districts[ward.id]) {
                addAllConnected( other_ward, region, region_hash, ward_districts);
            }
        }
    }
    double getRegionPopulation(Vector<ward> region) {
        double population = 0;
        if( region == null) {
            return 0;
        }
        for( ward ward : region) {
            if( ward.has_census_results) {
                population += ward.population;
            } else {
                for(Demographic p : ward.demographics) {
                    population += p.population;
                }
            }
        }
        return population;
    }
}

Population balance

The population of each district is accumulated from the polygon populations. Then the kullbach-leibler divergence of this from an equal distribution is calculated. This is the "population imbalance".

class DistrictMap {
    double population_imbalance = 0;
    if( Settings.population_balance_weight > 0 || Settings.voting_power_balance_weight > 0) {
        for(int i = 0; i < dist_pops.length; i++) {
            if( districts.size() <= i) {
                dist_pops[i] = 0;

            } else {
                District district = districts.get(i);
                dist_pops[i] = district.getPopulation();
            }
            total_population += dist_pops[i];
        }
        double rtotpop = 1.0/ total_population;
        for(int i = 0; i < dist_pops.length; i++) {
            dist_pop_frac[i] = dist_pops[i] * rtotpop;
        }

        double exp_population = total_population/(double)dist_pops.length;
        //System.out.println("exp. pop. "+exp_population);
        for( int i = 0; i < perfect_dists.length; i++) {
            perfect_dists[i] = exp_population;
        }
        population_imbalance = getKLDiv(perfect_dists,dist_pops,1);
    }
    public double getKLDiv(double[] p, double[] q, double regularization_factor) {
        boolean verbose = false;
        if( regularization_factor == 1.2 || regularization_factor == 0.01) {
            if( false) {
                verbose = true;
                System.out.println(" reg: "+regularization_factor);
            }
            //regularization_factor = 1;
        }
        if( verbose) {
            for( int i = 0; i < p.length; i++) {
                System.out.println(" "+i+" p: "+p[i]+" q: "+q[i]);
            }

        }
        //regularize (see "regularization" in statistics)
        for( int i = 0; i < p.length; i++)
            p[i]+=regularization_factor;  
        for( int i = 0; i < q.length; i++)
            q[i]+=regularization_factor;  

        //get totals
        double totp = 0;
        for( int i = 0; i < p.length; i++)
            totp += p[i];  
        double totq = 0;
        for( int i = 0; i < q.length; i++)
            totq += q[i];  

        //make same ratio.
        double ratio = totp/totq;
        for( int i = 0; i < q.length; i++)
            q[i] *= ratio;  

        //normalize
        totp = 0;
        for( int i = 0; i < p.length; i++)
            totp += p[i];  
        for( int i = 0; i < p.length; i++)
            p[i] /= totp;
        totq = 0;
        for( int i = 0; i < q.length; i++)
            totq += q[i];  
        for( int i = 0; i < q.length; i++)
            q[i] /= totq;

        //get kldiv
        double div = 0;
        for( int i = 0; i < q.length; i++) {
            if( p[i] == 0) {
                continue;
            }
            double kl = p[i]*(Math.log(q[i]) - Math.log(p[i]));
            if( verbose) {
                System.out.println("i: "+i+" \tp: "+p[i]+" \tq:"+q[i]+" \tkl:"+kl);
            }
            div += kl;
        }
        return -div;
    }   
}