Python for samfunnsøkonomi

Forelesningsnotater - Espen Sirnes

8 - Git, IDE'er og et lite spill

Git

Vi har fra før lært hvordan vi kloner et git-repositorie og hvordan vi dytter endringer dit. Men den kanskje viktigste funksjonen til git er som verktøy for å samarbeide om og koordinere arbeid med kode. I denne forelesningen skal vi se litt nærmere på hvordan vi bruker git til dette.

For å forstå dette, oppretter vi et enkelt test-repositorie med en tekstfil, som vi kan teste git på.

Opprett et nytt repositorie med en tekstfil

Start med å opprette et repositorie du kaller "git-test":

  1. gå til https://github.com/ og logg eventuelt inn, om du ikke allerede er innlogget
  2. Trykk New
  3. Kall repositoriet "git-test"

Du skal så klone dette repositoriet. Finn en egnet plassering, for eksempel ved å lage en ny mappe med navn "SOK-1005".

  1. Åpne kommandovinduet
  2. Skriv inn cd <ønsket plassering>, for eksempel cd SOK-1005.
  3. Hent et token (husk å merke av for "repo"), eller bruk et du allerede har, og lim det inn i følgende kommando:
git clone https://<token>@github.com/<brukernavn>/git-test

(bytt ut \<token> og \<brukernavn> med ditt token og brukernavn)

  1. Opprett en ny tekstfil i den nye mappen "git-test". I windows og jupyter.uit.no gjør du det ved å høyreklikke i mappen.

  2. Åpne tekstfilen og kopier inn følgende linjer med tekst:

To be, or not to be, that is the question:
Whether 'tis nobler in the mind to suffer
The slings and arrows of outrageous fortune,
Or to take Arms against a Sea of troubles,
And by opposing end them?
  1. Dytt endringene til git-hub ved å skrive (HUSK bruk cd + mappenavn for å navigere deg inn i git-test):
git add .
git commit -m "added Shakespeare"
git push

Blir du bedt om brukernavn/passord, generer du et og bruker det som passord.

Konflikt

Til nå har vi forutsatt at bare én person arbeider med samme repositorie. Hele poenget med git er imidlertid samarbeid, og da oppstår det ofte "konflikt". Vi snakker ikke da om bitter uenighet, men at to personer gjør endringer i samme repositorie på en måte som git ikke klarer å ordne opp i.

Kort fortalt så klarer git fint å holde rede på at ulike personer endrer på ulike filer, men når ulike personer endrer samme fil oppstår det konflikt. La os simulere dette med den nye tekstfilen vi har laget.

Du skal nå ta rollene som begge parter i konflikten.

  1. Gå til repositoriet på github og endre " 'tis" i teksten til "it is"
  2. Gå til tekstfilen lokalt, og endre samme del av teksten til "this", og lukk tekstfilen.
  3. Forplikt den lokale endringen i kommandovinduet med
    git add .
    git commit -m "Improved Shakespeare"

Konfliktløsning

Du har nå skapt en konflikt med deg selv. For å løse konflikten må du kjøre

git pull

Du skal da få en melding om at det er en konflikt du må løse. Åpne tekstfilen lokalt på nytt. Du vil da se at den er endret til noe slikt som dette:

To be, or not to be, that is the question:
<<<<<<< HEAD
Whether this nobler in the mind to suffer
=======
Whether 'it is nobler in the mind to suffer
>>>>>>> 2de43c361c099727ae6f3cabce81a2fb87fe3e48
The slings and arrows of outrageous fortune,
Or to take Arms against a Sea of troubles,
And by opposing end them?

Partitet mellom <<<<<<< HEAD og >>>>>>> 2de43c361c099727ae6f3cabce81a2fb87fe3e48 er der konflikten befinner seg. Bytt ut teksten med

To be, or not to be, that is the question:
Whether it is nobler in the mind to suffer        #" 'tis" means "it is" NOT "this", can I read?
The slings and arrows of outrageous fortune,
Or to take Arms against a Sea of troubles,
And by opposing end them?

forplikt og dytt endringene til github:

git add .
git commit -m "Modernized Shakespeare"
git push

Du har nå løst konflikten.

Når konflikten er for stor og omfattende til å redigere i teksten

Av og til vil det være upraktisk å redigere i teksten, dersom endringene er for store og omfattende, eller dersom endringene for eksempel er i en jupyter-fil.

Git er laget for å samarbeide om kode, som er lagret i vanlige tekstfiler. Formatet til *.ipynb-filene som jupyter lager er også kode, men vi ser den ikke når vi åpner dokumentet i jupyter. Når git setter inn tekst i jupyter-filer, slik som over, vil den blir ubrukelig.

Den enkleste måten å løse slike situasjoner på, er å avbryte konfliktløsningen, omdøpe det lokale repositoriet og klone det originale fjernrepositoriet på på nytt. Det er ikke helt "etter boken", men å håndtere mer avanserte konflikter er det ikke plass til i dette kurset.

Om du har klonet repositoriet på nytt kan du åpne filen i repoen på nett og lokalt samtidig, og sammenligne. Du kan så endre filen i det klonede repositoriet, eller bytte ut filen i fjernrepositoriet med den nyere lokale filen. Dette gjør du på følgende måte:

  1. Avbryt konfliktløsningen med git merge --abort

  2. Omdøp eksisterende lokale repositorie til noe annet, for eksempel "git-test" til "git-test0".

  3. Klon fjernrepositoriet på github på nytt (husk "cd .." slik at du befinner deg i mappen over eksempelvis "git-test0")

  4. Sammenlign filene, gjør endringer eller lagre filen(e) i "git-test0" i det nyklonede repositoriet.

  5. Forplikt og dytt til github:

git add .
git commit -m "Fixed Shakespeare"
git push

Generelt er det langt mer nøyaktig og sikrere å bruke gits konfliktløsningsmetode enn å selv sammenligne versjoner, men dersom du vet helt sikkert at din lokale versjon er den riktige, eller at det er opplagt hvilke endringer som skal gjøres, kan du bruke metoden over.

Arbeidsflyt i git

Følgende arbeidsflyt anbefales for git:

  1. Kjør følgende kommandoer ofte:
git add .
git commit -m "Update"
git pull

Da vil din kode hele tiden være oppdatert mot originalkoden på serveren. Dersom du oppdager konflikter, kan du håndtere dem selv eller ta opp konflikten med de andre på prosjektet. Generelt er det en god idé at flere ikke jobber med samme fil samtidig.

  1. Du får beskjed når det skjer en konflikten etter å ha kjørt git pull. Søk da etter "<<<<<<< HEAD" i teksten for å finne hvor konfliktene ligger, og rediger bort git-taggene som vist i eksemplet over.

  2. Kjør alltid kommandoene i 1. før du dytter til orginalen på serveren (git push)

Dersom innloggingen ikke fungerer

En vanlig feil er at innloggingstedaljene som er lagret på maskinen din er utdaterte og feil. Når du bruker git vil ofte innloggingsdetaljene lagres på maskinen. Du kan løse problemet ved å slette detaljene

  • på Mac: Finn appen "Keychain Access", finn oppføringen for github, og slett den
  • På Windows: Trykk på vindu-knappen, søk og åpne "Credential Manager", finn oppføringen for github og slett den

Om du må skrive inn passord hver gang du pusher til github, så kan du åpne filen som heter "config" under mappen ".git" i repositoriet lokalt. Du kan åpne den med "notepad" (eller installer Notepad++). Her kan du sette inn tokenet etterfulgt av "@" før "github.com" i nettadressen. Altså, om tokenet er "abddefg", brukernavnet er "bruker" og repositoriet heter "mitt_repo", skal adressen i config-filen se slik ut: "https://abddefg@github.com/bruker/mitt_repo"

Du må muligens endre på visningsalternativene i "Windows File Explorer" til å vise skjulte filer og mapper, for å kunne se "config"-filen.

IDE'er

Integrated Development Environment (IDE) er et program som hjelper deg å programmere. I motsetning til jupyter, brukes IDE'er til å redigere tekstfiler.

Jupyter er fint til å redigere korte programsnutter og å hente inn og kjøre ferdigprogrammerte pakker og moduler. Men skal du skrive kode på mer en femten linjer, bør du skrive dem i en tekstfil.

En av de mest brukte IDE'ene er Visual Studio Code, som støtter en lang rekke programmeringsspråk, inkludert python.

Før du kan bruke VSCode med python, må du sørge for at python er installert. Last det ned her. Dersom du har Windows, så vær sikker på at du installerer for alle brukere (krever adminrettigheter) og at du haker av for at python skal legges til i PATH.

Blotto

Blotto er et spill der Oberst Blotto (du) skal disponere styrker over et gitt antall slagmarker. Spillet er sentralt innenfor økonomifaget og det som kalles spillteori. Vi kommer tilbake til noen interessante egenskaper med spillet senere, men la oss først spillet det.

Du laster ned spillet ved å klone det fra github. Du bruker da følgende kommando:

git clone https://github.com/espensirnes/blotto

Pass på at du er i ønsket mappe i kommandovinduet før du kloner. Les mer her om hvordan du navigerer i filsystemet i kommandovinduet. Naviger for eksempel til sok-1005-mappen du skapte over.

Du kan nå åpne spillet i VSCode. Om du har VSCode åpen, så går du på "File" og velger "Open Folder". Du kan så velge blotto-mappen du nettopp klonet.

Windows: For å lokalisere mappen du nettopp skapte i Windows, se hvilken bokstav stien begynner med, og se under "min Datamaskin"/"My Computer" for å gjenfinne rett stasjon.

Lag en python-fil

Om du nå har åpen VSCode i blotto-mappen, velger du "File"->"New File" og så "File"->"Save As..." , og lagerer den nye filen som "run.py".

"run.py" er nå en python-fil, som du kan kjøre pythonkode i. Skriv for eksempel print("Hello World") i filen og trykk ctrl+s for å lagre.

Før du kan kjøre koden i VSCode, må du konfigurere "debugging" (feilsøking).

  1. Trykk på run debug-knappen
  2. Under "Run and Debug", trykk på "create a launch.json file"
  3. På spørsmål om hvilken utvidelse (extension) som skal brukes, trykk på "install" under "Python intelliSense" fra Microsoft.
  4. Velg fanen "run.py" for å komme til bake til fila di.
  5. Velg "Run and Debug"

Du kan nå trykke på run debug-knappen oppe til venstre hver når du skal kjøre koden.

For å kjøre spillet skriver du inn:

import blotto
blotto.Run(6,100)

Du vil med stor sannsynlighet få en ModuleNotFoundError når du forsøker å laste blotto. Det skyldes i så fall at du ikke har installert alle pakkene du trenger. Om du har installert python riktig, for alle brukere, så kan du installere det du trenger med følgende kommandoer i kommandovinduet:

pip install tk
pip install Pillow
pip install numpy
  • Mac-brukere: skriv pip3 i stedet for pip
  • Windowsbrukere: pass på at du har åpnet kommandovinduet med adminrettigheter

Forhåndsdefinere strategier

Du kan legge inn en forhåndsdefinert strategi om du vil slippe å korrigere den i spillet hver gang. før blotto.Run() kan du for eksempel bestemme deg for å satse alle troppene på fire av feltene.

Om du nå limer inn strategien under i "run.py", før blotto.Run(6,100), og bytter ut sistnevnte med blotto.Run(6,100,player_strategy), vil dette bli din utgangsstrategi:

Eksempel 1:

In [1]:
import numpy as np

def player_strategy(n_battalions,n_fields):
    #defining the array:
    battalions=np.zeros(n_fields,dtype=int)
    
    #assigning 25 battalions to the first four battle fields:
    battalions[:4]=25
    
    #randomizing to make strategy less predictable
    battalions=battalions[np.random.rand(n_fields).argsort()]
    
    #asserting that all and no more than all battalions are used:
    assert sum(battalions)==n_battalions
    
    return battalions

Koden over må altså limes inn i "run.py".

Legg merke til at vi importerer numpy øverst, siden denne brukes til å lage en strategi. Du byttet ut blotto.Run(6,100) til blotto.Run(6,100,player_strategy) fordi det tredje argumentet i blotto.Run angir din utgangsstrategi strategi.

Du kan forresten alltid sjekke hvilke argumenter en funksjon i har i VSCodev som en funksjon tar ved å holde ned Alt mens du har pekere over funksjonen.

  • Start nå spillet på nytt, med den forhåndsdefinerte strategien.

Du kan endre koden over, og legge inn hvilken som helst strategi. I dette tilfellet er en startegi en funksjon som tar antall battaljoner og slagmarker som argumenter, og returnerer en liste over hvor battaljonene skal plasseres.

PC'ens strategier

PC'en sin strategi i dette spillet er foreløpig ikke veldig smart. Strategien over vil nesten alltid slå PC'en, så la oss hjelpe PC'en med en litt smartere strategi:

Eksempel 2:

In [2]:
def computer_strategy(n_battalions,n_fields):
    battalions=np.zeros(n_fields,dtype=int)
    
    #Will at present only work witih 100 battalions
    battalions[0:1]=8
    battalions[1:4]=30
    battalions[4:]=1
    
    #randomizing location to make strategy less predictable
    battalions=battalions[np.random.rand(n_fields).argsort()]
    
    assert sum(battalions)==n_battalions
    return battalions

Lim koden inn i "run.py", og legg navnet på funksjonen inn som fjerde argument iblotto.Run, slik at det blir blotto.Run(6,100,player_strategy, computer_strategy)

Om du ikke selv endrer på din forhåndsdefinerte strategi i spillet, vil du nå se at rollene er snudd om. Nå vinner PC'en alltid de fire siste slagmarkene så vidt. Problemet er at PC'en nå vet hva du skal velge. Det kan hjelpe litt å gjøre valgene dine tilfeldige, slik at PC'en ikke kan vite hvilke valg du kommer til å ta. Du kan skape litt tilfeldighet ved å legge følgende inn i strategien over, før assert:

battalions=battalions[np.random.rand(n_fields).argsort()]

Denne koden sørge for at plasseringen av battaljonene blir tilfeldig. Du vil nå tape ofte, men ikke alltid.

Testing av strategier

Det er upraktisk og veldig ineffektivt å kjøre blotto.run for å teste strategier. Det er enkelt å lage en funksjon som gir en poengsum for hvor god en strategi er mot en annen. Følgende funksjon returnerer en poengsum på -1 (tap), 0 (uavgjort) eller 1 (seier):

Eksempel 3:

In [3]:
def call_battle(n_fields, n_battalions, player_strategy, computer_strategy):
    c_battlions=computer_strategy(n_battalions,n_fields)
    p_battlions=player_strategy(n_battalions,n_fields)
    
    diff=p_battlions-c_battlions
    points=sum(diff>0)-sum(diff<0)
 
    return int(points>0)-int(points<0)

print(call_battle(6, 100, player_strategy, computer_strategy))
-1

Å sjekke bare ett spill kan vi imidlertid ikke dra noen konklusjon av. Følgende kode kjører call_battle 100 000 ganger, og returner et score fra -1 (alltid tap) til 1 (alltid seier):

Eksempel 4:

In [4]:
def test_strategies(n_fields,n_battalions,player_strategy, computer_strategy):
    n_tests=100000
    r=0
    record=[]
    for i in range(n_tests):
        p=call_battle(n_fields, n_battalions,
            player_strategy, computer_strategy)
        record.append(p)
        r+=p
    return r/n_tests

test_strategies(6,100,player_strategy, computer_strategy)
Out[4]:
-0.79813

Du kan nå gjøre endringer i både player_strategy og computer_strategy for å finne best mulig strategi, som fungerer mot flest mulig andre strategier!

Bruk funksjonaliteten i IDE'en

Når du skal forsøke å lage best mulig strategi, kan det være lurt å benytte seg av mulighetene som ligger i IDE'en. Den store fordelen med en IDE er at du kan stoppe koden, og undersøke variabler under veis. Trykker du i margen til venstre, vil det komme et rødt punkt som indikerer et stoppunkt.

Oppgaver:

3) Gå sammen to eller flere og gjør følgende: a) Lag et repositorie 'blottokopi' på github som deles med alle b) Klon repositoriet og kopier innholdet i blottorepositoriet til 'blottokopi' c) Nå kan dere løse oppgavene under på hver deres PC, men arbeide på samme dokument d) Forsøk å lage og løse konflikter, for å se hvordan det fungerer

1) Forsøk å endre på funksjonen player_strategy slik at den blir best mulig (gjør gjerne endringer i computer_strategy også for å gi den best mulig konkurranse), og lagre strategien/funksjonen i en fil du kaller "strategy.py" i sok-1005-repositoriet. Koden vil bli kjørt mot den beste strategien til foreleser 100 000 ganger og får en score mellom -1 (alltid tap) og 1 (alltid seier). Du vil få vite hvordan du er rangert.

Det må være gjort større endringer i funksjonen enn bare å endre på antall bataljoner i player_strategy, for å få godkjent.

2) Endre computer_strategy og player_strategy slik at du faktisk kan angi vilkårlig hvilket antall battalioner og slagmarker du skal ha (nå vil du få en assert error om du oppgir noe annet enn hhv. 100 og 6.