parent
8a13c33b93
commit
d4a079268a
@ -1,241 +0,0 @@ |
|||||||
<# |
|
||||||
.Synopsis |
|
||||||
Activate a Python virtual environment for the current PowerShell session. |
|
||||||
|
|
||||||
.Description |
|
||||||
Pushes the python executable for a virtual environment to the front of the |
|
||||||
$Env:PATH environment variable and sets the prompt to signify that you are |
|
||||||
in a Python virtual environment. Makes use of the command line switches as |
|
||||||
well as the `pyvenv.cfg` file values present in the virtual environment. |
|
||||||
|
|
||||||
.Parameter VenvDir |
|
||||||
Path to the directory that contains the virtual environment to activate. The |
|
||||||
default value for this is the parent of the directory that the Activate.ps1 |
|
||||||
script is located within. |
|
||||||
|
|
||||||
.Parameter Prompt |
|
||||||
The prompt prefix to display when this virtual environment is activated. By |
|
||||||
default, this prompt is the name of the virtual environment folder (VenvDir) |
|
||||||
surrounded by parentheses and followed by a single space (ie. '(.venv) '). |
|
||||||
|
|
||||||
.Example |
|
||||||
Activate.ps1 |
|
||||||
Activates the Python virtual environment that contains the Activate.ps1 script. |
|
||||||
|
|
||||||
.Example |
|
||||||
Activate.ps1 -Verbose |
|
||||||
Activates the Python virtual environment that contains the Activate.ps1 script, |
|
||||||
and shows extra information about the activation as it executes. |
|
||||||
|
|
||||||
.Example |
|
||||||
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv |
|
||||||
Activates the Python virtual environment located in the specified location. |
|
||||||
|
|
||||||
.Example |
|
||||||
Activate.ps1 -Prompt "MyPython" |
|
||||||
Activates the Python virtual environment that contains the Activate.ps1 script, |
|
||||||
and prefixes the current prompt with the specified string (surrounded in |
|
||||||
parentheses) while the virtual environment is active. |
|
||||||
|
|
||||||
.Notes |
|
||||||
On Windows, it may be required to enable this Activate.ps1 script by setting the |
|
||||||
execution policy for the user. You can do this by issuing the following PowerShell |
|
||||||
command: |
|
||||||
|
|
||||||
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser |
|
||||||
|
|
||||||
For more information on Execution Policies: |
|
||||||
https://go.microsoft.com/fwlink/?LinkID=135170 |
|
||||||
|
|
||||||
#> |
|
||||||
Param( |
|
||||||
[Parameter(Mandatory = $false)] |
|
||||||
[String] |
|
||||||
$VenvDir, |
|
||||||
[Parameter(Mandatory = $false)] |
|
||||||
[String] |
|
||||||
$Prompt |
|
||||||
) |
|
||||||
|
|
||||||
<# Function declarations --------------------------------------------------- #> |
|
||||||
|
|
||||||
<# |
|
||||||
.Synopsis |
|
||||||
Remove all shell session elements added by the Activate script, including the |
|
||||||
addition of the virtual environment's Python executable from the beginning of |
|
||||||
the PATH variable. |
|
||||||
|
|
||||||
.Parameter NonDestructive |
|
||||||
If present, do not remove this function from the global namespace for the |
|
||||||
session. |
|
||||||
|
|
||||||
#> |
|
||||||
function global:deactivate ([switch]$NonDestructive) { |
|
||||||
# Revert to original values |
|
||||||
|
|
||||||
# The prior prompt: |
|
||||||
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) { |
|
||||||
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt |
|
||||||
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT |
|
||||||
} |
|
||||||
|
|
||||||
# The prior PYTHONHOME: |
|
||||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) { |
|
||||||
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME |
|
||||||
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME |
|
||||||
} |
|
||||||
|
|
||||||
# The prior PATH: |
|
||||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) { |
|
||||||
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH |
|
||||||
Remove-Item -Path Env:_OLD_VIRTUAL_PATH |
|
||||||
} |
|
||||||
|
|
||||||
# Just remove the VIRTUAL_ENV altogether: |
|
||||||
if (Test-Path -Path Env:VIRTUAL_ENV) { |
|
||||||
Remove-Item -Path env:VIRTUAL_ENV |
|
||||||
} |
|
||||||
|
|
||||||
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether: |
|
||||||
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) { |
|
||||||
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force |
|
||||||
} |
|
||||||
|
|
||||||
# Leave deactivate function in the global namespace if requested: |
|
||||||
if (-not $NonDestructive) { |
|
||||||
Remove-Item -Path function:deactivate |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
<# |
|
||||||
.Description |
|
||||||
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the |
|
||||||
given folder, and returns them in a map. |
|
||||||
|
|
||||||
For each line in the pyvenv.cfg file, if that line can be parsed into exactly |
|
||||||
two strings separated by `=` (with any amount of whitespace surrounding the =) |
|
||||||
then it is considered a `key = value` line. The left hand string is the key, |
|
||||||
the right hand is the value. |
|
||||||
|
|
||||||
If the value starts with a `'` or a `"` then the first and last character is |
|
||||||
stripped from the value before being captured. |
|
||||||
|
|
||||||
.Parameter ConfigDir |
|
||||||
Path to the directory that contains the `pyvenv.cfg` file. |
|
||||||
#> |
|
||||||
function Get-PyVenvConfig( |
|
||||||
[String] |
|
||||||
$ConfigDir |
|
||||||
) { |
|
||||||
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg" |
|
||||||
|
|
||||||
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue). |
|
||||||
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue |
|
||||||
|
|
||||||
# An empty map will be returned if no config file is found. |
|
||||||
$pyvenvConfig = @{ } |
|
||||||
|
|
||||||
if ($pyvenvConfigPath) { |
|
||||||
|
|
||||||
Write-Verbose "File exists, parse `key = value` lines" |
|
||||||
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath |
|
||||||
|
|
||||||
$pyvenvConfigContent | ForEach-Object { |
|
||||||
$keyval = $PSItem -split "\s*=\s*", 2 |
|
||||||
if ($keyval[0] -and $keyval[1]) { |
|
||||||
$val = $keyval[1] |
|
||||||
|
|
||||||
# Remove extraneous quotations around a string value. |
|
||||||
if ("'""".Contains($val.Substring(0, 1))) { |
|
||||||
$val = $val.Substring(1, $val.Length - 2) |
|
||||||
} |
|
||||||
|
|
||||||
$pyvenvConfig[$keyval[0]] = $val |
|
||||||
Write-Verbose "Adding Key: '$($keyval[0])'='$val'" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return $pyvenvConfig |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
<# Begin Activate script --------------------------------------------------- #> |
|
||||||
|
|
||||||
# Determine the containing directory of this script |
|
||||||
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition |
|
||||||
$VenvExecDir = Get-Item -Path $VenvExecPath |
|
||||||
|
|
||||||
Write-Verbose "Activation script is located in path: '$VenvExecPath'" |
|
||||||
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)" |
|
||||||
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)" |
|
||||||
|
|
||||||
# Set values required in priority: CmdLine, ConfigFile, Default |
|
||||||
# First, get the location of the virtual environment, it might not be |
|
||||||
# VenvExecDir if specified on the command line. |
|
||||||
if ($VenvDir) { |
|
||||||
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values" |
|
||||||
} |
|
||||||
else { |
|
||||||
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir." |
|
||||||
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/") |
|
||||||
Write-Verbose "VenvDir=$VenvDir" |
|
||||||
} |
|
||||||
|
|
||||||
# Next, read the `pyvenv.cfg` file to determine any required value such |
|
||||||
# as `prompt`. |
|
||||||
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir |
|
||||||
|
|
||||||
# Next, set the prompt from the command line, or the config file, or |
|
||||||
# just use the name of the virtual environment folder. |
|
||||||
if ($Prompt) { |
|
||||||
Write-Verbose "Prompt specified as argument, using '$Prompt'" |
|
||||||
} |
|
||||||
else { |
|
||||||
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value" |
|
||||||
if ($pyvenvCfg -and $pyvenvCfg['prompt']) { |
|
||||||
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'" |
|
||||||
$Prompt = $pyvenvCfg['prompt']; |
|
||||||
} |
|
||||||
else { |
|
||||||
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virutal environment)" |
|
||||||
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'" |
|
||||||
$Prompt = Split-Path -Path $venvDir -Leaf |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
Write-Verbose "Prompt = '$Prompt'" |
|
||||||
Write-Verbose "VenvDir='$VenvDir'" |
|
||||||
|
|
||||||
# Deactivate any currently active virtual environment, but leave the |
|
||||||
# deactivate function in place. |
|
||||||
deactivate -nondestructive |
|
||||||
|
|
||||||
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine |
|
||||||
# that there is an activated venv. |
|
||||||
$env:VIRTUAL_ENV = $VenvDir |
|
||||||
|
|
||||||
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) { |
|
||||||
|
|
||||||
Write-Verbose "Setting prompt to '$Prompt'" |
|
||||||
|
|
||||||
# Set the prompt to include the env name |
|
||||||
# Make sure _OLD_VIRTUAL_PROMPT is global |
|
||||||
function global:_OLD_VIRTUAL_PROMPT { "" } |
|
||||||
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT |
|
||||||
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt |
|
||||||
|
|
||||||
function global:prompt { |
|
||||||
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) " |
|
||||||
_OLD_VIRTUAL_PROMPT |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
# Clear PYTHONHOME |
|
||||||
if (Test-Path -Path Env:PYTHONHOME) { |
|
||||||
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME |
|
||||||
Remove-Item -Path Env:PYTHONHOME |
|
||||||
} |
|
||||||
|
|
||||||
# Add the venv to the PATH |
|
||||||
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH |
|
||||||
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH" |
|
@ -1,66 +0,0 @@ |
|||||||
# This file must be used with "source bin/activate" *from bash* |
|
||||||
# you cannot run it directly |
|
||||||
|
|
||||||
deactivate () { |
|
||||||
# reset old environment variables |
|
||||||
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then |
|
||||||
PATH="${_OLD_VIRTUAL_PATH:-}" |
|
||||||
export PATH |
|
||||||
unset _OLD_VIRTUAL_PATH |
|
||||||
fi |
|
||||||
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then |
|
||||||
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" |
|
||||||
export PYTHONHOME |
|
||||||
unset _OLD_VIRTUAL_PYTHONHOME |
|
||||||
fi |
|
||||||
|
|
||||||
# This should detect bash and zsh, which have a hash command that must |
|
||||||
# be called to get it to forget past commands. Without forgetting |
|
||||||
# past commands the $PATH changes we made may not be respected |
|
||||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then |
|
||||||
hash -r 2> /dev/null |
|
||||||
fi |
|
||||||
|
|
||||||
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then |
|
||||||
PS1="${_OLD_VIRTUAL_PS1:-}" |
|
||||||
export PS1 |
|
||||||
unset _OLD_VIRTUAL_PS1 |
|
||||||
fi |
|
||||||
|
|
||||||
unset VIRTUAL_ENV |
|
||||||
if [ ! "${1:-}" = "nondestructive" ] ; then |
|
||||||
# Self destruct! |
|
||||||
unset -f deactivate |
|
||||||
fi |
|
||||||
} |
|
||||||
|
|
||||||
# unset irrelevant variables |
|
||||||
deactivate nondestructive |
|
||||||
|
|
||||||
VIRTUAL_ENV="/Users/Zalum/Desktop/Tesi/PyCTBN/venv" |
|
||||||
export VIRTUAL_ENV |
|
||||||
|
|
||||||
_OLD_VIRTUAL_PATH="$PATH" |
|
||||||
PATH="$VIRTUAL_ENV/bin:$PATH" |
|
||||||
export PATH |
|
||||||
|
|
||||||
# unset PYTHONHOME if set |
|
||||||
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway) |
|
||||||
# could use `if (set -u; : $PYTHONHOME) ;` in bash |
|
||||||
if [ -n "${PYTHONHOME:-}" ] ; then |
|
||||||
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}" |
|
||||||
unset PYTHONHOME |
|
||||||
fi |
|
||||||
|
|
||||||
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then |
|
||||||
_OLD_VIRTUAL_PS1="${PS1:-}" |
|
||||||
PS1="(venv) ${PS1:-}" |
|
||||||
export PS1 |
|
||||||
fi |
|
||||||
|
|
||||||
# This should detect bash and zsh, which have a hash command that must |
|
||||||
# be called to get it to forget past commands. Without forgetting |
|
||||||
# past commands the $PATH changes we made may not be respected |
|
||||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then |
|
||||||
hash -r 2> /dev/null |
|
||||||
fi |
|
@ -1,25 +0,0 @@ |
|||||||
# This file must be used with "source bin/activate.csh" *from csh*. |
|
||||||
# You cannot run it directly. |
|
||||||
# Created by Davide Di Blasi <davidedb@gmail.com>. |
|
||||||
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com> |
|
||||||
|
|
||||||
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test "\!:*" != "nondestructive" && unalias deactivate' |
|
||||||
|
|
||||||
# Unset irrelevant variables. |
|
||||||
deactivate nondestructive |
|
||||||
|
|
||||||
setenv VIRTUAL_ENV "/Users/Zalum/Desktop/Tesi/PyCTBN/venv" |
|
||||||
|
|
||||||
set _OLD_VIRTUAL_PATH="$PATH" |
|
||||||
setenv PATH "$VIRTUAL_ENV/bin:$PATH" |
|
||||||
|
|
||||||
|
|
||||||
set _OLD_VIRTUAL_PROMPT="$prompt" |
|
||||||
|
|
||||||
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then |
|
||||||
set prompt = "(venv) $prompt" |
|
||||||
endif |
|
||||||
|
|
||||||
alias pydoc python -m pydoc |
|
||||||
|
|
||||||
rehash |
|
@ -1,64 +0,0 @@ |
|||||||
# This file must be used with "source <venv>/bin/activate.fish" *from fish* |
|
||||||
# (https://fishshell.com/); you cannot run it directly. |
|
||||||
|
|
||||||
function deactivate -d "Exit virtual environment and return to normal shell environment" |
|
||||||
# reset old environment variables |
|
||||||
if test -n "$_OLD_VIRTUAL_PATH" |
|
||||||
set -gx PATH $_OLD_VIRTUAL_PATH |
|
||||||
set -e _OLD_VIRTUAL_PATH |
|
||||||
end |
|
||||||
if test -n "$_OLD_VIRTUAL_PYTHONHOME" |
|
||||||
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME |
|
||||||
set -e _OLD_VIRTUAL_PYTHONHOME |
|
||||||
end |
|
||||||
|
|
||||||
if test -n "$_OLD_FISH_PROMPT_OVERRIDE" |
|
||||||
functions -e fish_prompt |
|
||||||
set -e _OLD_FISH_PROMPT_OVERRIDE |
|
||||||
functions -c _old_fish_prompt fish_prompt |
|
||||||
functions -e _old_fish_prompt |
|
||||||
end |
|
||||||
|
|
||||||
set -e VIRTUAL_ENV |
|
||||||
if test "$argv[1]" != "nondestructive" |
|
||||||
# Self-destruct! |
|
||||||
functions -e deactivate |
|
||||||
end |
|
||||||
end |
|
||||||
|
|
||||||
# Unset irrelevant variables. |
|
||||||
deactivate nondestructive |
|
||||||
|
|
||||||
set -gx VIRTUAL_ENV "/Users/Zalum/Desktop/Tesi/PyCTBN/venv" |
|
||||||
|
|
||||||
set -gx _OLD_VIRTUAL_PATH $PATH |
|
||||||
set -gx PATH "$VIRTUAL_ENV/bin" $PATH |
|
||||||
|
|
||||||
# Unset PYTHONHOME if set. |
|
||||||
if set -q PYTHONHOME |
|
||||||
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME |
|
||||||
set -e PYTHONHOME |
|
||||||
end |
|
||||||
|
|
||||||
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" |
|
||||||
# fish uses a function instead of an env var to generate the prompt. |
|
||||||
|
|
||||||
# Save the current fish_prompt function as the function _old_fish_prompt. |
|
||||||
functions -c fish_prompt _old_fish_prompt |
|
||||||
|
|
||||||
# With the original prompt function renamed, we can override with our own. |
|
||||||
function fish_prompt |
|
||||||
# Save the return status of the last command. |
|
||||||
set -l old_status $status |
|
||||||
|
|
||||||
# Output the venv prompt; color taken from the blue of the Python logo. |
|
||||||
printf "%s%s%s" (set_color 4B8BBE) "(venv) " (set_color normal) |
|
||||||
|
|
||||||
# Restore the return status of the previous command. |
|
||||||
echo "exit $old_status" | . |
|
||||||
# Output the original/"old" prompt. |
|
||||||
_old_fish_prompt |
|
||||||
end |
|
||||||
|
|
||||||
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" |
|
||||||
end |
|
@ -1,8 +0,0 @@ |
|||||||
#!/Users/Zalum/Desktop/Tesi/PyCTBN/venv/bin/python |
|
||||||
# -*- coding: utf-8 -*- |
|
||||||
import re |
|
||||||
import sys |
|
||||||
from setuptools.command.easy_install import main |
|
||||||
if __name__ == '__main__': |
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) |
|
||||||
sys.exit(main()) |
|
@ -1,8 +0,0 @@ |
|||||||
#!/Users/Zalum/Desktop/Tesi/PyCTBN/venv/bin/python |
|
||||||
# -*- coding: utf-8 -*- |
|
||||||
import re |
|
||||||
import sys |
|
||||||
from setuptools.command.easy_install import main |
|
||||||
if __name__ == '__main__': |
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) |
|
||||||
sys.exit(main()) |
|
@ -1,8 +0,0 @@ |
|||||||
#!/Users/Zalum/Desktop/Tesi/PyCTBN/venv/bin/python |
|
||||||
# -*- coding: utf-8 -*- |
|
||||||
import re |
|
||||||
import sys |
|
||||||
from numpy.f2py.f2py2e import main |
|
||||||
if __name__ == '__main__': |
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) |
|
||||||
sys.exit(main()) |
|
@ -1,8 +0,0 @@ |
|||||||
#!/Users/Zalum/Desktop/Tesi/PyCTBN/venv/bin/python |
|
||||||
# -*- coding: utf-8 -*- |
|
||||||
import re |
|
||||||
import sys |
|
||||||
from numpy.f2py.f2py2e import main |
|
||||||
if __name__ == '__main__': |
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) |
|
||||||
sys.exit(main()) |
|
@ -1,8 +0,0 @@ |
|||||||
#!/Users/Zalum/Desktop/Tesi/PyCTBN/venv/bin/python |
|
||||||
# -*- coding: utf-8 -*- |
|
||||||
import re |
|
||||||
import sys |
|
||||||
from numpy.f2py.f2py2e import main |
|
||||||
if __name__ == '__main__': |
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) |
|
||||||
sys.exit(main()) |
|
@ -1,8 +0,0 @@ |
|||||||
#!/Users/Zalum/Desktop/Tesi/PyCTBN/venv/bin/python |
|
||||||
# -*- coding: utf-8 -*- |
|
||||||
import re |
|
||||||
import sys |
|
||||||
from pip._internal.cli.main import main |
|
||||||
if __name__ == '__main__': |
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) |
|
||||||
sys.exit(main()) |
|
@ -1,8 +0,0 @@ |
|||||||
#!/Users/Zalum/Desktop/Tesi/PyCTBN/venv/bin/python |
|
||||||
# -*- coding: utf-8 -*- |
|
||||||
import re |
|
||||||
import sys |
|
||||||
from pip._internal.cli.main import main |
|
||||||
if __name__ == '__main__': |
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) |
|
||||||
sys.exit(main()) |
|
@ -1,8 +0,0 @@ |
|||||||
#!/Users/Zalum/Desktop/Tesi/PyCTBN/venv/bin/python |
|
||||||
# -*- coding: utf-8 -*- |
|
||||||
import re |
|
||||||
import sys |
|
||||||
from pip._internal.cli.main import main |
|
||||||
if __name__ == '__main__': |
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) |
|
||||||
sys.exit(main()) |
|
@ -1 +0,0 @@ |
|||||||
python3.9 |
|
@ -1 +0,0 @@ |
|||||||
python3.9 |
|
@ -1 +0,0 @@ |
|||||||
/usr/local/bin/python3.9 |
|
@ -1,8 +0,0 @@ |
|||||||
#!/Users/Zalum/Desktop/Tesi/PyCTBN/venv/bin/python |
|
||||||
# -*- coding: utf-8 -*- |
|
||||||
import re |
|
||||||
import sys |
|
||||||
from tqdm.cli import main |
|
||||||
if __name__ == '__main__': |
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) |
|
||||||
sys.exit(main()) |
|
@ -1,123 +0,0 @@ |
|||||||
import sys |
|
||||||
import os |
|
||||||
import re |
|
||||||
import importlib |
|
||||||
import warnings |
|
||||||
|
|
||||||
|
|
||||||
is_pypy = '__pypy__' in sys.builtin_module_names |
|
||||||
|
|
||||||
|
|
||||||
def warn_distutils_present(): |
|
||||||
if 'distutils' not in sys.modules: |
|
||||||
return |
|
||||||
if is_pypy and sys.version_info < (3, 7): |
|
||||||
# PyPy for 3.6 unconditionally imports distutils, so bypass the warning |
|
||||||
# https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250 |
|
||||||
return |
|
||||||
warnings.warn( |
|
||||||
"Distutils was imported before Setuptools, but importing Setuptools " |
|
||||||
"also replaces the `distutils` module in `sys.modules`. This may lead " |
|
||||||
"to undesirable behaviors or errors. To avoid these issues, avoid " |
|
||||||
"using distutils directly, ensure that setuptools is installed in the " |
|
||||||
"traditional way (e.g. not an editable install), and/or make sure " |
|
||||||
"that setuptools is always imported before distutils.") |
|
||||||
|
|
||||||
|
|
||||||
def clear_distutils(): |
|
||||||
if 'distutils' not in sys.modules: |
|
||||||
return |
|
||||||
warnings.warn("Setuptools is replacing distutils.") |
|
||||||
mods = [name for name in sys.modules if re.match(r'distutils\b', name)] |
|
||||||
for name in mods: |
|
||||||
del sys.modules[name] |
|
||||||
|
|
||||||
|
|
||||||
def enabled(): |
|
||||||
""" |
|
||||||
Allow selection of distutils by environment variable. |
|
||||||
""" |
|
||||||
which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'stdlib') |
|
||||||
return which == 'local' |
|
||||||
|
|
||||||
|
|
||||||
def ensure_local_distutils(): |
|
||||||
clear_distutils() |
|
||||||
distutils = importlib.import_module('setuptools._distutils') |
|
||||||
distutils.__name__ = 'distutils' |
|
||||||
sys.modules['distutils'] = distutils |
|
||||||
|
|
||||||
# sanity check that submodules load as expected |
|
||||||
core = importlib.import_module('distutils.core') |
|
||||||
assert '_distutils' in core.__file__, core.__file__ |
|
||||||
|
|
||||||
|
|
||||||
def do_override(): |
|
||||||
""" |
|
||||||
Ensure that the local copy of distutils is preferred over stdlib. |
|
||||||
|
|
||||||
See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401 |
|
||||||
for more motivation. |
|
||||||
""" |
|
||||||
if enabled(): |
|
||||||
warn_distutils_present() |
|
||||||
ensure_local_distutils() |
|
||||||
|
|
||||||
|
|
||||||
class DistutilsMetaFinder: |
|
||||||
def find_spec(self, fullname, path, target=None): |
|
||||||
if path is not None: |
|
||||||
return |
|
||||||
|
|
||||||
method_name = 'spec_for_{fullname}'.format(**locals()) |
|
||||||
method = getattr(self, method_name, lambda: None) |
|
||||||
return method() |
|
||||||
|
|
||||||
def spec_for_distutils(self): |
|
||||||
import importlib.abc |
|
||||||
import importlib.util |
|
||||||
|
|
||||||
class DistutilsLoader(importlib.abc.Loader): |
|
||||||
|
|
||||||
def create_module(self, spec): |
|
||||||
return importlib.import_module('setuptools._distutils') |
|
||||||
|
|
||||||
def exec_module(self, module): |
|
||||||
pass |
|
||||||
|
|
||||||
return importlib.util.spec_from_loader('distutils', DistutilsLoader()) |
|
||||||
|
|
||||||
def spec_for_pip(self): |
|
||||||
""" |
|
||||||
Ensure stdlib distutils when running under pip. |
|
||||||
See pypa/pip#8761 for rationale. |
|
||||||
""" |
|
||||||
if self.pip_imported_during_build(): |
|
||||||
return |
|
||||||
clear_distutils() |
|
||||||
self.spec_for_distutils = lambda: None |
|
||||||
|
|
||||||
@staticmethod |
|
||||||
def pip_imported_during_build(): |
|
||||||
""" |
|
||||||
Detect if pip is being imported in a build script. Ref #2355. |
|
||||||
""" |
|
||||||
import traceback |
|
||||||
return any( |
|
||||||
frame.f_globals['__file__'].endswith('setup.py') |
|
||||||
for frame, line in traceback.walk_stack(None) |
|
||||||
) |
|
||||||
|
|
||||||
|
|
||||||
DISTUTILS_FINDER = DistutilsMetaFinder() |
|
||||||
|
|
||||||
|
|
||||||
def add_shim(): |
|
||||||
sys.meta_path.insert(0, DISTUTILS_FINDER) |
|
||||||
|
|
||||||
|
|
||||||
def remove_shim(): |
|
||||||
try: |
|
||||||
sys.meta_path.remove(DISTUTILS_FINDER) |
|
||||||
except ValueError: |
|
||||||
pass |
|
@ -1 +0,0 @@ |
|||||||
__import__('_distutils_hack').do_override() |
|
@ -1,8 +0,0 @@ |
|||||||
# -*- coding: utf-8 -*- |
|
||||||
try: |
|
||||||
from ._version import version as __version__ |
|
||||||
except ImportError: |
|
||||||
__version__ = 'unknown' |
|
||||||
|
|
||||||
__all__ = ['easter', 'parser', 'relativedelta', 'rrule', 'tz', |
|
||||||
'utils', 'zoneinfo'] |
|
@ -1,43 +0,0 @@ |
|||||||
""" |
|
||||||
Common code used in multiple modules. |
|
||||||
""" |
|
||||||
|
|
||||||
|
|
||||||
class weekday(object): |
|
||||||
__slots__ = ["weekday", "n"] |
|
||||||
|
|
||||||
def __init__(self, weekday, n=None): |
|
||||||
self.weekday = weekday |
|
||||||
self.n = n |
|
||||||
|
|
||||||
def __call__(self, n): |
|
||||||
if n == self.n: |
|
||||||
return self |
|
||||||
else: |
|
||||||
return self.__class__(self.weekday, n) |
|
||||||
|
|
||||||
def __eq__(self, other): |
|
||||||
try: |
|
||||||
if self.weekday != other.weekday or self.n != other.n: |
|
||||||
return False |
|
||||||
except AttributeError: |
|
||||||
return False |
|
||||||
return True |
|
||||||
|
|
||||||
def __hash__(self): |
|
||||||
return hash(( |
|
||||||
self.weekday, |
|
||||||
self.n, |
|
||||||
)) |
|
||||||
|
|
||||||
def __ne__(self, other): |
|
||||||
return not (self == other) |
|
||||||
|
|
||||||
def __repr__(self): |
|
||||||
s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday] |
|
||||||
if not self.n: |
|
||||||
return s |
|
||||||
else: |
|
||||||
return "%s(%+d)" % (s, self.n) |
|
||||||
|
|
||||||
# vim:ts=4:sw=4:et |
|
@ -1,4 +0,0 @@ |
|||||||
# coding: utf-8 |
|
||||||
# file generated by setuptools_scm |
|
||||||
# don't change, don't track in version control |
|
||||||
version = '2.8.1' |
|
@ -1,89 +0,0 @@ |
|||||||
# -*- coding: utf-8 -*- |
|
||||||
""" |
|
||||||
This module offers a generic easter computing method for any given year, using |
|
||||||
Western, Orthodox or Julian algorithms. |
|
||||||
""" |
|
||||||
|
|
||||||
import datetime |
|
||||||
|
|
||||||
__all__ = ["easter", "EASTER_JULIAN", "EASTER_ORTHODOX", "EASTER_WESTERN"] |
|
||||||
|
|
||||||
EASTER_JULIAN = 1 |
|
||||||
EASTER_ORTHODOX = 2 |
|
||||||
EASTER_WESTERN = 3 |
|
||||||
|
|
||||||
|
|
||||||
def easter(year, method=EASTER_WESTERN): |
|
||||||
""" |
|
||||||
This method was ported from the work done by GM Arts, |
|
||||||
on top of the algorithm by Claus Tondering, which was |
|
||||||
based in part on the algorithm of Ouding (1940), as |
|
||||||
quoted in "Explanatory Supplement to the Astronomical |
|
||||||
Almanac", P. Kenneth Seidelmann, editor. |
|
||||||
|
|
||||||
This algorithm implements three different easter |
|
||||||
calculation methods: |
|
||||||
|
|
||||||
1 - Original calculation in Julian calendar, valid in |
|
||||||
dates after 326 AD |
|
||||||
2 - Original method, with date converted to Gregorian |
|
||||||
calendar, valid in years 1583 to 4099 |
|
||||||
3 - Revised method, in Gregorian calendar, valid in |
|
||||||
years 1583 to 4099 as well |
|
||||||
|
|
||||||
These methods are represented by the constants: |
|
||||||
|
|
||||||
* ``EASTER_JULIAN = 1`` |
|
||||||
* ``EASTER_ORTHODOX = 2`` |
|
||||||
* ``EASTER_WESTERN = 3`` |
|
||||||
|
|
||||||
The default method is method 3. |
|
||||||
|
|
||||||
More about the algorithm may be found at: |
|
||||||
|
|
||||||
`GM Arts: Easter Algorithms <http://www.gmarts.org/index.php?go=415>`_ |
|
||||||
|
|
||||||
and |
|
||||||
|
|
||||||
`The Calendar FAQ: Easter <https://www.tondering.dk/claus/cal/easter.php>`_ |
|
||||||
|
|
||||||
""" |
|
||||||
|
|
||||||
if not (1 <= method <= 3): |
|
||||||
raise ValueError("invalid method") |
|
||||||
|
|
||||||
# g - Golden year - 1 |
|
||||||
# c - Century |
|
||||||
# h - (23 - Epact) mod 30 |
|
||||||
# i - Number of days from March 21 to Paschal Full Moon |
|
||||||
# j - Weekday for PFM (0=Sunday, etc) |
|
||||||
# p - Number of days from March 21 to Sunday on or before PFM |
|
||||||
# (-6 to 28 methods 1 & 3, to 56 for method 2) |
|
||||||
# e - Extra days to add for method 2 (converting Julian |
|
||||||
# date to Gregorian date) |
|
||||||
|
|
||||||
y = year |
|
||||||
g = y % 19 |
|
||||||
e = 0 |
|
||||||
if method < 3: |
|
||||||
# Old method |
|
||||||
i = (19*g + 15) % 30 |
|
||||||
j = (y + y//4 + i) % 7 |
|
||||||
if method == 2: |
|
||||||
# Extra dates to convert Julian to Gregorian date |
|
||||||
e = 10 |
|
||||||
if y > 1600: |
|
||||||
e = e + y//100 - 16 - (y//100 - 16)//4 |
|
||||||
else: |
|
||||||
# New method |
|
||||||
c = y//100 |
|
||||||
h = (c - c//4 - (8*c + 13)//25 + 19*g + 15) % 30 |
|
||||||
i = h - (h//28)*(1 - (h//28)*(29//(h + 1))*((21 - g)//11)) |
|
||||||
j = (y + y//4 + i + 2 - c + c//4) % 7 |
|
||||||
|
|
||||||
# p can be from -6 to 56 corresponding to dates 22 March to 23 May |
|
||||||
# (later dates apply to method 2, although 23 May never actually occurs) |
|
||||||
p = i - j + e |
|
||||||
d = 1 + (p + 27 + (p + 6)//40) % 31 |
|
||||||
m = 3 + (p + 26)//30 |
|
||||||
return datetime.date(int(y), int(m), int(d)) |
|
@ -1,61 +0,0 @@ |
|||||||
# -*- coding: utf-8 -*- |
|
||||||
from ._parser import parse, parser, parserinfo, ParserError |
|
||||||
from ._parser import DEFAULTPARSER, DEFAULTTZPARSER |
|
||||||
from ._parser import UnknownTimezoneWarning |
|
||||||
|
|
||||||
from ._parser import __doc__ |
|
||||||
|
|
||||||
from .isoparser import isoparser, isoparse |
|
||||||
|
|
||||||
__all__ = ['parse', 'parser', 'parserinfo', |
|
||||||
'isoparse', 'isoparser', |
|
||||||
'ParserError', |
|
||||||
'UnknownTimezoneWarning'] |
|
||||||
|
|
||||||
|
|
||||||
### |
|
||||||
# Deprecate portions of the private interface so that downstream code that |
|
||||||
# is improperly relying on it is given *some* notice. |
|
||||||
|
|
||||||
|
|
||||||
def __deprecated_private_func(f): |
|
||||||
from functools import wraps |
|
||||||
import warnings |
|
||||||
|
|
||||||
msg = ('{name} is a private function and may break without warning, ' |
|
||||||
'it will be moved and or renamed in future versions.') |
|
||||||
msg = msg.format(name=f.__name__) |
|
||||||
|
|
||||||
@wraps(f) |
|
||||||
def deprecated_func(*args, **kwargs): |
|
||||||
warnings.warn(msg, DeprecationWarning) |
|
||||||
return f(*args, **kwargs) |
|
||||||
|
|
||||||
return deprecated_func |
|
||||||
|
|
||||||
def __deprecate_private_class(c): |
|
||||||
import warnings |
|
||||||
|
|
||||||
msg = ('{name} is a private class and may break without warning, ' |
|
||||||
'it will be moved and or renamed in future versions.') |
|
||||||
msg = msg.format(name=c.__name__) |
|
||||||
|
|
||||||
class private_class(c): |
|
||||||
__doc__ = c.__doc__ |
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs): |
|
||||||
warnings.warn(msg, DeprecationWarning) |
|
||||||
super(private_class, self).__init__(*args, **kwargs) |
|
||||||
|
|
||||||
private_class.__name__ = c.__name__ |
|
||||||
|
|
||||||
return private_class |
|
||||||
|
|
||||||
|
|
||||||
from ._parser import _timelex, _resultbase |
|
||||||
from ._parser import _tzparser, _parsetz |
|
||||||
|
|
||||||
_timelex = __deprecate_private_class(_timelex) |
|
||||||
_tzparser = __deprecate_private_class(_tzparser) |
|
||||||
_resultbase = __deprecate_private_class(_resultbase) |
|
||||||
_parsetz = __deprecated_private_func(_parsetz) |
|
File diff suppressed because it is too large
Load Diff
@ -1,411 +0,0 @@ |
|||||||
# -*- coding: utf-8 -*- |
|
||||||
""" |
|
||||||
This module offers a parser for ISO-8601 strings |
|
||||||
|
|
||||||
It is intended to support all valid date, time and datetime formats per the |
|
||||||
ISO-8601 specification. |
|
||||||
|
|
||||||
..versionadded:: 2.7.0 |
|
||||||
""" |
|
||||||
from datetime import datetime, timedelta, time, date |
|
||||||
import calendar |
|
||||||
from dateutil import tz |
|
||||||
|
|
||||||
from functools import wraps |
|
||||||
|
|
||||||
import re |
|
||||||
import six |
|
||||||
|
|
||||||
__all__ = ["isoparse", "isoparser"] |
|
||||||
|
|
||||||
|
|
||||||
def _takes_ascii(f): |
|
||||||
@wraps(f) |
|
||||||
def func(self, str_in, *args, **kwargs): |
|
||||||
# If it's a stream, read the whole thing |
|
||||||
str_in = getattr(str_in, 'read', lambda: str_in)() |
|
||||||
|
|
||||||
# If it's unicode, turn it into bytes, since ISO-8601 only covers ASCII |
|
||||||
if isinstance(str_in, six.text_type): |
|
||||||
# ASCII is the same in UTF-8 |
|
||||||
try: |
|
||||||
str_in = str_in.encode('ascii') |
|
||||||
except UnicodeEncodeError as e: |
|
||||||
msg = 'ISO-8601 strings should contain only ASCII characters' |
|
||||||
six.raise_from(ValueError(msg), e) |
|
||||||
|
|
||||||
return f(self, str_in, *args, **kwargs) |
|
||||||
|
|
||||||
return func |
|
||||||
|
|
||||||
|
|
||||||
class isoparser(object): |
|
||||||
def __init__(self, sep=None): |
|
||||||
""" |
|
||||||
:param sep: |
|
||||||
A single character that separates date and time portions. If |
|
||||||
``None``, the parser will accept any single character. |
|
||||||
For strict ISO-8601 adherence, pass ``'T'``. |
|
||||||
""" |
|
||||||
if sep is not None: |
|
||||||
if (len(sep) != 1 or ord(sep) >= 128 or sep in '0123456789'): |
|
||||||
raise ValueError('Separator must be a single, non-numeric ' + |
|
||||||
'ASCII character') |
|
||||||
|
|
||||||
sep = sep.encode('ascii') |
|
||||||
|
|
||||||
self._sep = sep |
|
||||||
|
|
||||||
@_takes_ascii |
|
||||||
def isoparse(self, dt_str): |
|
||||||
""" |
|
||||||
Parse an ISO-8601 datetime string into a :class:`datetime.datetime`. |
|
||||||
|
|
||||||
An ISO-8601 datetime string consists of a date portion, followed |
|
||||||
optionally by a time portion - the date and time portions are separated |
|
||||||
by a single character separator, which is ``T`` in the official |
|
||||||
standard. Incomplete date formats (such as ``YYYY-MM``) may *not* be |
|
||||||
combined with a time portion. |
|
||||||
|
|
||||||
Supported date formats are: |
|
||||||
|
|
||||||
Common: |
|
||||||
|
|
||||||
- ``YYYY`` |
|
||||||
- ``YYYY-MM`` or ``YYYYMM`` |
|
||||||
- ``YYYY-MM-DD`` or ``YYYYMMDD`` |
|
||||||
|
|
||||||
Uncommon: |
|
||||||
|
|
||||||
- ``YYYY-Www`` or ``YYYYWww`` - ISO week (day defaults to 0) |
|
||||||
- ``YYYY-Www-D`` or ``YYYYWwwD`` - ISO week and day |
|
||||||
|
|
||||||
The ISO week and day numbering follows the same logic as |
|
||||||
:func:`datetime.date.isocalendar`. |
|
||||||
|
|
||||||
Supported time formats are: |
|
||||||
|
|
||||||
- ``hh`` |
|
||||||
- ``hh:mm`` or ``hhmm`` |
|
||||||
- ``hh:mm:ss`` or ``hhmmss`` |
|
||||||
- ``hh:mm:ss.ssssss`` (Up to 6 sub-second digits) |
|
||||||
|
|
||||||
Midnight is a special case for `hh`, as the standard supports both |
|
||||||
00:00 and 24:00 as a representation. The decimal separator can be |
|
||||||
either a dot or a comma. |
|
||||||
|
|
||||||
|
|
||||||
.. caution:: |
|
||||||
|
|
||||||
Support for fractional components other than seconds is part of the |
|
||||||
ISO-8601 standard, but is not currently implemented in this parser. |
|
||||||
|
|
||||||
Supported time zone offset formats are: |
|
||||||
|
|
||||||
- `Z` (UTC) |
|
||||||
- `±HH:MM` |
|
||||||
- `±HHMM` |
|
||||||
- `±HH` |
|
||||||
|
|
||||||
Offsets will be represented as :class:`dateutil.tz.tzoffset` objects, |
|
||||||
with the exception of UTC, which will be represented as |
|
||||||
:class:`dateutil.tz.tzutc`. Time zone offsets equivalent to UTC (such |
|
||||||
as `+00:00`) will also be represented as :class:`dateutil.tz.tzutc`. |
|
||||||
|
|
||||||
:param dt_str: |
|
||||||
A string or stream containing only an ISO-8601 datetime string |
|
||||||
|
|
||||||
:return: |
|
||||||
Returns a :class:`datetime.datetime` representing the string. |
|
||||||
Unspecified components default to their lowest value. |
|
||||||
|
|
||||||
.. warning:: |
|
||||||
|
|
||||||
As of version 2.7.0, the strictness of the parser should not be |
|
||||||
considered a stable part of the contract. Any valid ISO-8601 string |
|
||||||
that parses correctly with the default settings will continue to |
|
||||||
parse correctly in future versions, but invalid strings that |
|
||||||
currently fail (e.g. ``2017-01-01T00:00+00:00:00``) are not |
|
||||||
guaranteed to continue failing in future versions if they encode |
|
||||||
a valid date. |
|
||||||
|
|
||||||
.. versionadded:: 2.7.0 |
|
||||||
""" |
|
||||||
components, pos = self._parse_isodate(dt_str) |
|
||||||
|
|
||||||
if len(dt_str) > pos: |
|
||||||
if self._sep is None or dt_str[pos:pos + 1] == self._sep: |
|
||||||
components += self._parse_isotime(dt_str[pos + 1:]) |
|
||||||
else: |
|
||||||
raise ValueError('String contains unknown ISO components') |
|
||||||
|
|
||||||
if len(components) > 3 and components[3] == 24: |
|
||||||
components[3] = 0 |
|
||||||
return datetime(*components) + timedelta(days=1) |
|
||||||
|
|
||||||
return datetime(*components) |
|
||||||
|
|
||||||
@_takes_ascii |
|
||||||
def parse_isodate(self, datestr): |
|
||||||
""" |
|
||||||
Parse the date portion of an ISO string. |
|
||||||
|
|
||||||
:param datestr: |
|
||||||
The string portion of an ISO string, without a separator |
|
||||||
|
|
||||||
:return: |
|
||||||
Returns a :class:`datetime.date` object |
|
||||||
""" |
|
||||||
components, pos = self._parse_isodate(datestr) |
|
||||||
if pos < len(datestr): |
|
||||||
raise ValueError('String contains unknown ISO ' + |
|
||||||
'components: {}'.format(datestr)) |
|
||||||
return date(*components) |
|
||||||
|
|
||||||
@_takes_ascii |
|
||||||
def parse_isotime(self, timestr): |
|
||||||
""" |
|
||||||
Parse the time portion of an ISO string. |
|
||||||
|
|
||||||
:param timestr: |
|
||||||
The time portion of an ISO string, without a separator |
|
||||||
|
|
||||||
:return: |
|
||||||
Returns a :class:`datetime.time` object |
|
||||||
""" |
|
||||||
components = self._parse_isotime(timestr) |
|
||||||
if components[0] == 24: |
|
||||||
components[0] = 0 |
|
||||||
return time(*components) |
|
||||||
|
|
||||||
@_takes_ascii |
|
||||||
def parse_tzstr(self, tzstr, zero_as_utc=True): |
|
||||||
""" |
|
||||||
Parse a valid ISO time zone string. |
|
||||||
|
|
||||||
See :func:`isoparser.isoparse` for details on supported formats. |
|
||||||
|
|
||||||
:param tzstr: |
|
||||||
A string representing an ISO time zone offset |
|
||||||
|
|
||||||
:param zero_as_utc: |
|
||||||
Whether to return :class:`dateutil.tz.tzutc` for zero-offset zones |
|
||||||
|
|
||||||
:return: |
|
||||||
Returns :class:`dateutil.tz.tzoffset` for offsets and |
|
||||||
:class:`dateutil.tz.tzutc` for ``Z`` and (if ``zero_as_utc`` is |
|
||||||
specified) offsets equivalent to UTC. |
|
||||||
""" |
|
||||||
return self._parse_tzstr(tzstr, zero_as_utc=zero_as_utc) |
|
||||||
|
|
||||||
# Constants |
|
||||||
_DATE_SEP = b'-' |
|
||||||
_TIME_SEP = b':' |
|
||||||
_FRACTION_REGEX = re.compile(b'[\\.,]([0-9]+)') |
|
||||||
|
|
||||||
def _parse_isodate(self, dt_str): |
|
||||||
try: |
|
||||||
return self._parse_isodate_common(dt_str) |
|
||||||
except ValueError: |
|
||||||
return self._parse_isodate_uncommon(dt_str) |
|
||||||
|
|
||||||
def _parse_isodate_common(self, dt_str): |
|
||||||
len_str = len(dt_str) |
|
||||||
components = [1, 1, 1] |
|
||||||
|
|
||||||
if len_str < 4: |
|
||||||
raise ValueError('ISO string too short') |
|
||||||
|
|
||||||
# Year |
|
||||||
components[0] = int(dt_str[0:4]) |
|
||||||
pos = 4 |
|
||||||
if pos >= len_str: |
|
||||||
return components, pos |
|
||||||
|
|
||||||
has_sep = dt_str[pos:pos + 1] == self._DATE_SEP |
|
||||||
if has_sep: |
|
||||||
pos += 1 |
|
||||||
|
|
||||||
# Month |
|
||||||
if len_str - pos < 2: |
|
||||||
raise ValueError('Invalid common month') |
|
||||||
|
|
||||||
components[1] = int(dt_str[pos:pos + 2]) |
|
||||||
pos += 2 |
|
||||||
|
|
||||||
if pos >= len_str: |
|
||||||
if has_sep: |
|
||||||
return components, pos |
|
||||||
else: |
|
||||||
raise ValueError('Invalid ISO format') |
|
||||||
|
|
||||||
if has_sep: |
|
||||||
if dt_str[pos:pos + 1] != self._DATE_SEP: |
|
||||||
raise ValueError('Invalid separator in ISO string') |
|
||||||
pos += 1 |
|
||||||
|
|
||||||
# Day |
|
||||||
if len_str - pos < 2: |
|
||||||
raise ValueError('Invalid common day') |
|
||||||
components[2] = int(dt_str[pos:pos + 2]) |
|
||||||
return components, pos + 2 |
|
||||||
|
|
||||||
def _parse_isodate_uncommon(self, dt_str): |
|
||||||
if len(dt_str) < 4: |
|
||||||
raise ValueError('ISO string too short') |
|
||||||
|
|
||||||
# All ISO formats start with the year |
|
||||||
year = int(dt_str[0:4]) |
|
||||||
|
|
||||||
has_sep = dt_str[4:5] == self._DATE_SEP |
|
||||||
|
|
||||||
pos = 4 + has_sep # Skip '-' if it's there |
|
||||||
if dt_str[pos:pos + 1] == b'W': |
|
||||||
# YYYY-?Www-?D? |
|
||||||
pos += 1 |
|
||||||
weekno = int(dt_str[pos:pos + 2]) |
|
||||||
pos += 2 |
|
||||||
|
|
||||||
dayno = 1 |
|
||||||
if len(dt_str) > pos: |
|
||||||
if (dt_str[pos:pos + 1] == self._DATE_SEP) != has_sep: |
|
||||||
raise ValueError('Inconsistent use of dash separator') |
|
||||||
|
|
||||||
pos += has_sep |
|
||||||
|
|
||||||
dayno = int(dt_str[pos:pos + 1]) |
|
||||||
pos += 1 |
|
||||||
|
|
||||||
base_date = self._calculate_weekdate(year, weekno, dayno) |
|
||||||
else: |
|
||||||
# YYYYDDD or YYYY-DDD |
|
||||||
if len(dt_str) - pos < 3: |
|
||||||
raise ValueError('Invalid ordinal day') |
|
||||||
|
|
||||||
ordinal_day = int(dt_str[pos:pos + 3]) |
|
||||||
pos += 3 |
|
||||||
|
|
||||||
if ordinal_day < 1 or ordinal_day > (365 + calendar.isleap(year)): |
|
||||||
raise ValueError('Invalid ordinal day' + |
|
||||||
' {} for year {}'.format(ordinal_day, year)) |
|
||||||
|
|
||||||
base_date = date(year, 1, 1) + timedelta(days=ordinal_day - 1) |
|
||||||
|
|
||||||
components = [base_date.year, base_date.month, base_date.day] |
|
||||||
return components, pos |
|
||||||
|
|
||||||
def _calculate_weekdate(self, year, week, day): |
|
||||||
""" |
|
||||||
Calculate the day of corresponding to the ISO year-week-day calendar. |
|
||||||
|
|
||||||
This function is effectively the inverse of |
|
||||||
:func:`datetime.date.isocalendar`. |
|
||||||
|
|
||||||
:param year: |
|
||||||
The year in the ISO calendar |
|
||||||
|
|
||||||
:param week: |
|
||||||
The week in the ISO calendar - range is [1, 53] |
|
||||||
|
|
||||||
:param day: |
|
||||||
The day in the ISO calendar - range is [1 (MON), 7 (SUN)] |
|
||||||
|
|
||||||
:return: |
|
||||||
Returns a :class:`datetime.date` |
|
||||||
""" |
|
||||||
if not 0 < week < 54: |
|
||||||
raise ValueError('Invalid week: {}'.format(week)) |
|
||||||
|
|
||||||
if not 0 < day < 8: # Range is 1-7 |
|
||||||
raise ValueError('Invalid weekday: {}'.format(day)) |
|
||||||
|
|
||||||
# Get week 1 for the specific year: |
|
||||||
jan_4 = date(year, 1, 4) # Week 1 always has January 4th in it |
|
||||||
week_1 = jan_4 - timedelta(days=jan_4.isocalendar()[2] - 1) |
|
||||||
|
|
||||||
# Now add the specific number of weeks and days to get what we want |
|
||||||
week_offset = (week - 1) * 7 + (day - 1) |
|
||||||
return week_1 + timedelta(days=week_offset) |
|
||||||
|
|
||||||
def _parse_isotime(self, timestr): |
|
||||||
len_str = len(timestr) |
|
||||||
components = [0, 0, 0, 0, None] |
|
||||||
pos = 0 |
|
||||||
comp = -1 |
|
||||||
|
|
||||||
if len(timestr) < 2: |
|
||||||
raise ValueError('ISO time too short') |
|
||||||
|
|
||||||
has_sep = len_str >= 3 and timestr[2:3] == self._TIME_SEP |
|
||||||
|
|
||||||
while pos < len_str and comp < 5: |
|
||||||
comp += 1 |
|
||||||
|
|
||||||
if timestr[pos:pos + 1] in b'-+Zz': |
|
||||||
# Detect time zone boundary |
|
||||||
components[-1] = self._parse_tzstr(timestr[pos:]) |
|
||||||
pos = len_str |
|
||||||
break |
|
||||||
|
|
||||||
if comp < 3: |
|
||||||
# Hour, minute, second |
|
||||||
components[comp] = int(timestr[pos:pos + 2]) |
|
||||||
pos += 2 |
|
||||||
if (has_sep and pos < len_str and |
|
||||||
timestr[pos:pos + 1] == self._TIME_SEP): |
|
||||||
pos += 1 |
|
||||||
|
|
||||||
if comp == 3: |
|
||||||
# Fraction of a second |
|
||||||
frac = self._FRACTION_REGEX.match(timestr[pos:]) |
|
||||||
if not frac: |
|
||||||
continue |
|
||||||
|
|
||||||
us_str = frac.group(1)[:6] # Truncate to microseconds |
|
||||||
components[comp] = int(us_str) * 10**(6 - len(us_str)) |
|
||||||
pos += len(frac.group()) |
|
||||||
|
|
||||||
if pos < len_str: |
|
||||||
raise ValueError('Unused components in ISO string') |
|
||||||
|
|
||||||
if components[0] == 24: |
|
||||||
# Standard supports 00:00 and 24:00 as representations of midnight |
|
||||||
if any(component != 0 for component in components[1:4]): |
|
||||||
raise ValueError('Hour may only be 24 at 24:00:00.000') |
|
||||||
|
|
||||||
return components |
|
||||||
|
|
||||||
def _parse_tzstr(self, tzstr, zero_as_utc=True): |
|
||||||
if tzstr == b'Z' or tzstr == b'z': |
|
||||||
return tz.UTC |
|
||||||
|
|
||||||
if len(tzstr) not in {3, 5, 6}: |
|
||||||
raise ValueError('Time zone offset must be 1, 3, 5 or 6 characters') |
|
||||||
|
|
||||||
if tzstr[0:1] == b'-': |
|
||||||
mult = -1 |
|
||||||
elif tzstr[0:1] == b'+': |
|
||||||
mult = 1 |
|
||||||
else: |
|
||||||
raise ValueError('Time zone offset requires sign') |
|
||||||
|
|
||||||
hours = int(tzstr[1:3]) |
|
||||||
if len(tzstr) == 3: |
|
||||||
minutes = 0 |
|
||||||
else: |
|
||||||
minutes = int(tzstr[(4 if tzstr[3:4] == self._TIME_SEP else 3):]) |
|
||||||
|
|
||||||
if zero_as_utc and hours == 0 and minutes == 0: |
|
||||||
return tz.UTC |
|
||||||
else: |
|
||||||
if minutes > 59: |
|
||||||
raise ValueError('Invalid minutes in time zone offset') |
|
||||||
|
|
||||||
if hours > 23: |
|
||||||
raise ValueError('Invalid hours in time zone offset') |
|
||||||
|
|
||||||
return tz.tzoffset(None, mult * (hours * 60 + minutes) * 60) |
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_ISOPARSER = isoparser() |
|
||||||
isoparse = DEFAULT_ISOPARSER.isoparse |
|
@ -1,599 +0,0 @@ |
|||||||
# -*- coding: utf-8 -*- |
|
||||||
import datetime |
|
||||||
import calendar |
|
||||||
|
|
||||||
import operator |
|
||||||
from math import copysign |
|
||||||
|
|
||||||
from six import integer_types |
|
||||||
from warnings import warn |
|
||||||
|
|
||||||
from ._common import weekday |
|
||||||
|
|
||||||
MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7)) |
|
||||||
|
|
||||||
__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"] |
|
||||||
|
|
||||||
|
|
||||||
class relativedelta(object): |
|
||||||
""" |
|
||||||
The relativedelta type is designed to be applied to an existing datetime and |
|
||||||
can replace specific components of that datetime, or represents an interval |
|
||||||
of time. |
|
||||||
|
|
||||||
It is based on the specification of the excellent work done by M.-A. Lemburg |
|
||||||
in his |
|
||||||
`mx.DateTime <https://www.egenix.com/products/python/mxBase/mxDateTime/>`_ extension. |
|
||||||
However, notice that this type does *NOT* implement the same algorithm as |
|
||||||
his work. Do *NOT* expect it to behave like mx.DateTime's counterpart. |
|
||||||
|
|
||||||
There are two different ways to build a relativedelta instance. The |
|
||||||
first one is passing it two date/datetime classes:: |
|
||||||
|
|
||||||
relativedelta(datetime1, datetime2) |
|
||||||
|
|
||||||
The second one is passing it any number of the following keyword arguments:: |
|
||||||
|
|
||||||
relativedelta(arg1=x,arg2=y,arg3=z...) |
|
||||||
|
|
||||||
year, month, day, hour, minute, second, microsecond: |
|
||||||
Absolute information (argument is singular); adding or subtracting a |
|
||||||
relativedelta with absolute information does not perform an arithmetic |
|
||||||
operation, but rather REPLACES the corresponding value in the |
|
||||||
original datetime with the value(s) in relativedelta. |
|
||||||
|
|
||||||
years, months, weeks, days, hours, minutes, seconds, microseconds: |
|
||||||
Relative information, may be negative (argument is plural); adding |
|
||||||
or subtracting a relativedelta with relative information performs |
|
||||||
the corresponding arithmetic operation on the original datetime value |
|
||||||
with the information in the relativedelta. |
|
||||||
|
|
||||||
weekday: |
|
||||||
One of the weekday instances (MO, TU, etc) available in the |
|
||||||
relativedelta module. These instances may receive a parameter N, |
|
||||||
specifying the Nth weekday, which could be positive or negative |
|
||||||
(like MO(+1) or MO(-2)). Not specifying it is the same as specifying |
|
||||||
+1. You can also use an integer, where 0=MO. This argument is always |
|
||||||
relative e.g. if the calculated date is already Monday, using MO(1) |
|
||||||
or MO(-1) won't change the day. To effectively make it absolute, use |
|
||||||
it in combination with the day argument (e.g. day=1, MO(1) for first |
|
||||||
Monday of the month). |
|
||||||
|
|
||||||
leapdays: |
|
||||||
Will add given days to the date found, if year is a leap |
|
||||||
year, and the date found is post 28 of february. |
|
||||||
|
|
||||||
yearday, nlyearday: |
|
||||||
Set the yearday or the non-leap year day (jump leap days). |
|
||||||
These are converted to day/month/leapdays information. |
|
||||||
|
|
||||||
There are relative and absolute forms of the keyword |
|
||||||
arguments. The plural is relative, and the singular is |
|
||||||
absolute. For each argument in the order below, the absolute form |
|
||||||
is applied first (by setting each attribute to that value) and |
|
||||||
then the relative form (by adding the value to the attribute). |
|
||||||
|
|
||||||
The order of attributes considered when this relativedelta is |
|
||||||
added to a datetime is: |
|
||||||
|
|
||||||
1. Year |
|
||||||
2. Month |
|
||||||
3. Day |
|
||||||
4. Hours |
|
||||||
5. Minutes |
|
||||||
6. Seconds |
|
||||||
7. Microseconds |
|
||||||
|
|
||||||
Finally, weekday is applied, using the rule described above. |
|
||||||
|
|
||||||
For example |
|
||||||
|
|
||||||
>>> from datetime import datetime |
|
||||||
>>> from dateutil.relativedelta import relativedelta, MO |
|
||||||
>>> dt = datetime(2018, 4, 9, 13, 37, 0) |
|
||||||
>>> delta = relativedelta(hours=25, day=1, weekday=MO(1)) |
|
||||||
>>> dt + delta |
|
||||||
datetime.datetime(2018, 4, 2, 14, 37) |
|
||||||
|
|
||||||
First, the day is set to 1 (the first of the month), then 25 hours |
|
||||||
are added, to get to the 2nd day and 14th hour, finally the |
|
||||||
weekday is applied, but since the 2nd is already a Monday there is |
|
||||||
no effect. |
|
||||||
|
|
||||||
""" |
|
||||||
|
|
||||||
def __init__(self, dt1=None, dt2=None, |
|
||||||
years=0, months=0, days=0, leapdays=0, weeks=0, |
|
||||||
hours=0, minutes=0, seconds=0, microseconds=0, |
|
||||||
year=None, month=None, day=None, weekday=None, |
|
||||||
yearday=None, nlyearday=None, |
|
||||||
hour=None, minute=None, second=None, microsecond=None): |
|
||||||
|
|
||||||
if dt1 and dt2: |
|
||||||
# datetime is a subclass of date. So both must be date |
|
||||||
if not (isinstance(dt1, datetime.date) and |
|
||||||
isinstance(dt2, datetime.date)): |
|
||||||
raise TypeError("relativedelta only diffs datetime/date") |
|
||||||
|
|
||||||
# We allow two dates, or two datetimes, so we coerce them to be |
|
||||||
# of the same type |
|
||||||
if (isinstance(dt1, datetime.datetime) != |
|
||||||
isinstance(dt2, datetime.datetime)): |
|
||||||
if not isinstance(dt1, datetime.datetime): |
|
||||||
dt1 = datetime.datetime.fromordinal(dt1.toordinal()) |
|
||||||
elif not isinstance(dt2, datetime.datetime): |
|
||||||
dt2 = datetime.datetime.fromordinal(dt2.toordinal()) |
|
||||||
|
|
||||||
self.years = 0 |
|
||||||
self.months = 0 |
|
||||||
self.days = 0 |
|
||||||
self.leapdays = 0 |
|
||||||
self.hours = 0 |
|
||||||
self.minutes = 0 |
|
||||||
self.seconds = 0 |
|
||||||
self.microseconds = 0 |
|
||||||
self.year = None |
|
||||||
self.month = None |
|
||||||
self.day = None |
|
||||||
self.weekday = None |
|
||||||
self.hour = None |
|
||||||
self.minute = None |
|
||||||
self.second = None |
|
||||||
self.microsecond = None |
|
||||||
self._has_time = 0 |
|
||||||
|
|
||||||
# Get year / month delta between the two |
|
||||||
months = (dt1.year - dt2.year) * 12 + (dt1.month - dt2.month) |
|
||||||
self._set_months(months) |
|
||||||
|
|
||||||
# Remove the year/month delta so the timedelta is just well-defined |
|
||||||
# time units (seconds, days and microseconds) |
|
||||||
dtm = self.__radd__(dt2) |
|
||||||
|
|
||||||
# If we've overshot our target, make an adjustment |
|
||||||
if dt1 < dt2: |
|
||||||
compare = operator.gt |
|
||||||
increment = 1 |
|
||||||
else: |
|
||||||
compare = operator.lt |
|
||||||
increment = -1 |
|
||||||
|
|
||||||
while compare(dt1, dtm): |
|
||||||
months += increment |
|
||||||
self._set_months(months) |
|
||||||
dtm = self.__radd__(dt2) |
|
||||||
|
|
||||||
# Get the timedelta between the "months-adjusted" date and dt1 |
|
||||||
delta = dt1 - dtm |
|
||||||
self.seconds = delta.seconds + delta.days * 86400 |
|
||||||
self.microseconds = delta.microseconds |
|
||||||
else: |
|
||||||
# Check for non-integer values in integer-only quantities |
|
||||||
if any(x is not None and x != int(x) for x in (years, months)): |
|
||||||
raise ValueError("Non-integer years and months are " |
|
||||||
"ambiguous and not currently supported.") |
|
||||||
|
|
||||||
# Relative information |
|
||||||
self.years = int(years) |
|
||||||
self.months = int(months) |
|
||||||
self.days = days + weeks * 7 |
|
||||||
self.leapdays = leapdays |
|
||||||
self.hours = hours |
|
||||||
self.minutes = minutes |
|
||||||
self.seconds = seconds |
|
||||||
self.microseconds = microseconds |
|
||||||
|
|
||||||
# Absolute information |
|
||||||
self.year = year |
|
||||||
self.month = month |
|
||||||
self.day = day |
|
||||||
self.hour = hour |
|
||||||
self.minute = minute |
|
||||||
self.second = second |
|
||||||
self.microsecond = microsecond |
|
||||||
|
|
||||||
if any(x is not None and int(x) != x |
|
||||||
for x in (year, month, day, hour, |
|
||||||
minute, second, microsecond)): |
|
||||||
# For now we'll deprecate floats - later it'll be an error. |
|
||||||
warn("Non-integer value passed as absolute information. " + |
|
||||||
"This is not a well-defined condition and will raise " + |
|
||||||
"errors in future versions.", DeprecationWarning) |
|
||||||
|
|
||||||
if isinstance(weekday, integer_types): |
|
||||||
self.weekday = weekdays[weekday] |
|
||||||
else: |
|
||||||
self.weekday = weekday |
|
||||||
|
|
||||||
yday = 0 |
|
||||||
if nlyearday: |
|
||||||
yday = nlyearday |
|
||||||
elif yearday: |
|
||||||
yday = yearday |
|
||||||
if yearday > 59: |
|
||||||
self.leapdays = -1 |
|
||||||
if yday: |
|
||||||
ydayidx = [31, 59, 90, 120, 151, 181, 212, |
|
||||||
243, 273, 304, 334, 366] |
|
||||||
for idx, ydays in enumerate(ydayidx): |
|
||||||
if yday <= ydays: |
|
||||||
self.month = idx+1 |
|
||||||
if idx == 0: |
|
||||||
self.day = yday |
|
||||||
else: |
|
||||||
self.day = yday-ydayidx[idx-1] |
|
||||||
break |
|
||||||
else: |
|
||||||
raise ValueError("invalid year day (%d)" % yday) |
|
||||||
|
|
||||||
self._fix() |
|
||||||
|
|
||||||
def _fix(self): |
|
||||||
if abs(self.microseconds) > 999999: |
|
||||||
s = _sign(self.microseconds) |
|
||||||
div, mod = divmod(self.microseconds * s, 1000000) |
|
||||||
self.microseconds = mod * s |
|
||||||
self.seconds += div * s |
|
||||||
if abs(self.seconds) > 59: |
|
||||||
s = _sign(self.seconds) |
|
||||||
div, mod = divmod(self.seconds * s, 60) |
|
||||||
self.seconds = mod * s |
|
||||||
self.minutes += div * s |
|
||||||
if abs(self.minutes) > 59: |
|
||||||
s = _sign(self.minutes) |
|
||||||
div, mod = divmod(self.minutes * s, 60) |
|
||||||
self.minutes = mod * s |
|
||||||
self.hours += div * s |
|
||||||
if abs(self.hours) > 23: |
|
||||||
s = _sign(self.hours) |
|
||||||
div, mod = divmod(self.hours * s, 24) |
|
||||||
self.hours = mod * s |
|
||||||
self.days += div * s |
|
||||||
if abs(self.months) > 11: |
|
||||||
s = _sign(self.months) |
|
||||||
div, mod = divmod(self.months * s, 12) |
|
||||||
self.months = mod * s |
|
||||||
self.years += div * s |
|
||||||
if (self.hours or self.minutes or self.seconds or self.microseconds |
|
||||||
or self.hour is not None or self.minute is not None or |
|
||||||
self.second is not None or self.microsecond is not None): |
|
||||||
self._has_time = 1 |
|
||||||
else: |
|
||||||
self._has_time = 0 |
|
||||||
|
|
||||||
@property |
|
||||||
def weeks(self): |
|
||||||
return int(self.days / 7.0) |
|
||||||
|
|
||||||
@weeks.setter |
|
||||||
def weeks(self, value): |
|
||||||
self.days = self.days - (self.weeks * 7) + value * 7 |
|
||||||
|
|
||||||
def _set_months(self, months): |
|
||||||
self.months = months |
|
||||||
if abs(self.months) > 11: |
|
||||||
s = _sign(self.months) |
|
||||||
div, mod = divmod(self.months * s, 12) |
|
||||||
self.months = mod * s |
|
||||||
self.years = div * s |
|
||||||
else: |
|
||||||
self.years = 0 |
|
||||||
|
|
||||||
def normalized(self): |
|
||||||
""" |
|
||||||
Return a version of this object represented entirely using integer |
|
||||||
values for the relative attributes. |
|
||||||
|
|
||||||
>>> relativedelta(days=1.5, hours=2).normalized() |
|
||||||
relativedelta(days=+1, hours=+14) |
|
||||||
|
|
||||||
:return: |
|
||||||
Returns a :class:`dateutil.relativedelta.relativedelta` object. |
|
||||||
""" |
|
||||||
# Cascade remainders down (rounding each to roughly nearest microsecond) |
|
||||||
days = int(self.days) |
|
||||||
|
|
||||||
hours_f = round(self.hours + 24 * (self.days - days), 11) |
|
||||||
hours = int(hours_f) |
|
||||||
|
|
||||||
minutes_f = round(self.minutes + 60 * (hours_f - hours), 10) |
|
||||||
minutes = int(minutes_f) |
|
||||||
|
|
||||||
seconds_f = round(self.seconds + 60 * (minutes_f - minutes), 8) |
|
||||||
seconds = int(seconds_f) |
|
||||||
|
|
||||||
microseconds = round(self.microseconds + 1e6 * (seconds_f - seconds)) |
|
||||||
|
|
||||||
# Constructor carries overflow back up with call to _fix() |
|
||||||
return self.__class__(years=self.years, months=self.months, |
|
||||||
days=days, hours=hours, minutes=minutes, |
|
||||||
seconds=seconds, microseconds=microseconds, |
|
||||||
leapdays=self.leapdays, year=self.year, |
|
||||||
month=self.month, day=self.day, |
|
||||||
weekday=self.weekday, hour=self.hour, |
|
||||||
minute=self.minute, second=self.second, |
|
||||||
microsecond=self.microsecond) |
|
||||||
|
|
||||||
def __add__(self, other): |
|
||||||
if isinstance(other, relativedelta): |
|
||||||
return self.__class__(years=other.years + self.years, |
|
||||||
months=other.months + self.months, |
|
||||||
days=other.days + self.days, |
|
||||||
hours=other.hours + self.hours, |
|
||||||
minutes=other.minutes + self.minutes, |
|
||||||
seconds=other.seconds + self.seconds, |
|
||||||
microseconds=(other.microseconds + |
|
||||||
self.microseconds), |
|
||||||
leapdays=other.leapdays or self.leapdays, |
|
||||||
year=(other.year if other.year is not None |
|
||||||
else self.year), |
|
||||||
month=(other.month if other.month is not None |
|
||||||
else self.month), |
|
||||||
day=(other.day if other.day is not None |
|
||||||
else self.day), |
|
||||||
weekday=(other.weekday if other.weekday is not None |
|
||||||
else self.weekday), |
|
||||||
hour=(other.hour if other.hour is not None |
|
||||||
else self.hour), |
|
||||||
minute=(other.minute if other.minute is not None |
|
||||||
else self.minute), |
|
||||||
second=(other.second if other.second is not None |
|
||||||
else self.second), |
|
||||||
microsecond=(other.microsecond if other.microsecond |
|
||||||
is not None else |
|
||||||
self.microsecond)) |
|
||||||
if isinstance(other, datetime.timedelta): |
|
||||||
return self.__class__(years=self.years, |
|
||||||
months=self.months, |
|
||||||
days=self.days + other.days, |
|
||||||
hours=self.hours, |
|
||||||
minutes=self.minutes, |
|
||||||
seconds=self.seconds + other.seconds, |
|
||||||
microseconds=self.microseconds + other.microseconds, |
|
||||||
leapdays=self.leapdays, |
|
||||||
year=self.year, |
|
||||||
month=self.month, |
|
||||||
day=self.day, |
|
||||||
weekday=self.weekday, |
|
||||||
hour=self.hour, |
|
||||||
minute=self.minute, |
|
||||||
second=self.second, |
|
||||||
microsecond=self.microsecond) |
|
||||||
if not isinstance(other, datetime.date): |
|
||||||
return NotImplemented |
|
||||||
elif self._has_time and not isinstance(other, datetime.datetime): |
|
||||||
other = datetime.datetime.fromordinal(other.toordinal()) |
|
||||||
year = (self.year or other.year)+self.years |
|
||||||
month = self.month or other.month |
|
||||||
if self.months: |
|
||||||
assert 1 <= abs(self.months) <= 12 |
|
||||||
month += self.months |
|
||||||
if month > 12: |
|
||||||
year += 1 |
|
||||||
month -= 12 |
|
||||||
elif month < 1: |
|
||||||
year -= 1 |
|
||||||
month += 12 |
|
||||||
day = min(calendar.monthrange(year, month)[1], |
|
||||||
self.day or other.day) |
|
||||||
repl = {"year": year, "month": month, "day": day} |
|
||||||
for attr in ["hour", "minute", "second", "microsecond"]: |
|
||||||
value = getattr(self, attr) |
|
||||||
if value is not None: |
|
||||||
repl[attr] = value |
|
||||||
days = self.days |
|
||||||
if self.leapdays and month > 2 and calendar.isleap(year): |
|
||||||
days += self.leapdays |
|
||||||
ret = (other.replace(**repl) |
|
||||||
+ datetime.timedelta(days=days, |
|
||||||
hours=self.hours, |
|
||||||
minutes=self.minutes, |
|
||||||
seconds=self.seconds, |
|
||||||
microseconds=self.microseconds)) |
|
||||||
if self.weekday: |
|
||||||
weekday, nth = self.weekday.weekday, self.weekday.n or 1 |
|
||||||
jumpdays = (abs(nth) - 1) * 7 |
|
||||||
if nth > 0: |
|
||||||
jumpdays += (7 - ret.weekday() + weekday) % 7 |
|
||||||
else: |
|
||||||
jumpdays += (ret.weekday() - weekday) % 7 |
|
||||||
jumpdays *= -1 |
|
||||||
ret += datetime.timedelta(days=jumpdays) |
|
||||||
return ret |
|
||||||
|
|
||||||
def __radd__(self, other): |
|
||||||
return self.__add__(other) |
|
||||||
|
|
||||||
def __rsub__(self, other): |
|
||||||
return self.__neg__().__radd__(other) |
|
||||||
|
|
||||||
def __sub__(self, other): |
|
||||||
if not isinstance(other, relativedelta): |
|
||||||
return NotImplemented # In case the other object defines __rsub__ |
|
||||||
return self.__class__(years=self.years - other.years, |
|
||||||
months=self.months - other.months, |
|
||||||
days=self.days - other.days, |
|
||||||
hours=self.hours - other.hours, |
|
||||||
minutes=self.minutes - other.minutes, |
|
||||||
seconds=self.seconds - other.seconds, |
|
||||||
microseconds=self.microseconds - other.microseconds, |
|
||||||
leapdays=self.leapdays or other.leapdays, |
|
||||||
year=(self.year if self.year is not None |
|
||||||
else other.year), |
|
||||||
month=(self.month if self.month is not None else |
|
||||||
other.month), |
|
||||||
day=(self.day if self.day is not None else |
|
||||||
other.day), |
|
||||||
weekday=(self.weekday if self.weekday is not None else |
|
||||||
other.weekday), |
|
||||||
hour=(self.hour if self.hour is not None else |
|
||||||
other.hour), |
|
||||||
minute=(self.minute if self.minute is not None else |
|
||||||
other.minute), |
|
||||||
second=(self.second if self.second is not None else |
|
||||||
other.second), |
|
||||||
microsecond=(self.microsecond if self.microsecond |
|
||||||
is not None else |
|
||||||
other.microsecond)) |
|
||||||
|
|
||||||
def __abs__(self): |
|
||||||
return self.__class__(years=abs(self.years), |
|
||||||
months=abs(self.months), |
|
||||||
days=abs(self.days), |
|
||||||
hours=abs(self.hours), |
|
||||||
minutes=abs(self.minutes), |
|
||||||
seconds=abs(self.seconds), |
|
||||||
microseconds=abs(self.microseconds), |
|
||||||
leapdays=self.leapdays, |
|
||||||
year=self.year, |
|
||||||
month=self.month, |
|
||||||
day=self.day, |
|
||||||
weekday=self.weekday, |
|
||||||
hour=self.hour, |
|
||||||
minute=self.minute, |
|
||||||
second=self.second, |
|
||||||
microsecond=self.microsecond) |
|
||||||
|
|
||||||
def __neg__(self): |
|
||||||
return self.__class__(years=-self.years, |
|
||||||
months=-self.months, |
|
||||||
days=-self.days, |
|
||||||
hours=-self.hours, |
|
||||||
minutes=-self.minutes, |
|
||||||
seconds=-self.seconds, |
|
||||||
microseconds=-self.microseconds, |
|
||||||
leapdays=self.leapdays, |
|
||||||
year=self.year, |
|
||||||
month=self.month, |
|
||||||
day=self.day, |
|
||||||
weekday=self.weekday, |
|
||||||
hour=self.hour, |
|
||||||
minute=self.minute, |
|
||||||
second=self.second, |
|
||||||
microsecond=self.microsecond) |
|
||||||
|
|
||||||
def __bool__(self): |
|
||||||
return not (not self.years and |
|
||||||
not self.months and |
|
||||||
not self.days and |
|
||||||
not self.hours and |
|
||||||
not self.minutes and |
|
||||||
not self.seconds and |
|
||||||
not self.microseconds and |
|
||||||
not self.leapdays and |
|
||||||
self.year is None and |
|
||||||
self.month is None and |
|
||||||
self.day is None and |
|
||||||
self.weekday is None and |
|
||||||
self.hour is None and |
|
||||||
self.minute is None and |
|
||||||
self.second is None and |
|
||||||
self.microsecond is None) |
|
||||||
# Compatibility with Python 2.x |
|
||||||
__nonzero__ = __bool__ |
|
||||||
|
|
||||||
def __mul__(self, other): |
|
||||||
try: |
|
||||||
f = float(other) |
|
||||||
except TypeError: |
|
||||||
return NotImplemented |
|
||||||
|
|
||||||
return self.__class__(years=int(self.years * f), |
|
||||||
months=int(self.months * f), |
|
||||||
days=int(self.days * f), |
|
||||||
hours=int(self.hours * f), |
|
||||||
minutes=int(self.minutes * f), |
|
||||||
seconds=int(self.seconds * f), |
|
||||||
microseconds=int(self.microseconds * f), |
|
||||||
leapdays=self.leapdays, |
|
||||||
year=self.year, |
|
||||||
month=self.month, |
|
||||||
day=self.day, |
|
||||||
weekday=self.weekday, |
|
||||||
hour=self.hour, |
|
||||||
minute=self.minute, |
|
||||||
second=self.second, |
|
||||||
microsecond=self.microsecond) |
|
||||||
|
|
||||||
__rmul__ = __mul__ |
|
||||||
|
|
||||||
def __eq__(self, other): |
|
||||||
if not isinstance(other, relativedelta): |
|
||||||
return NotImplemented |
|
||||||
if self.weekday or other.weekday: |
|
||||||
if not self.weekday or not other.weekday: |
|
||||||
return False |
|
||||||
if self.weekday.weekday != other.weekday.weekday: |
|
||||||
return False |
|
||||||
n1, n2 = self.weekday.n, other.weekday.n |
|
||||||
if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)): |
|
||||||
return False |
|
||||||
return (self.years == other.years and |
|
||||||
self.months == other.months and |
|
||||||
self.days == other.days and |
|
||||||
self.hours == other.hours and |
|
||||||
self.minutes == other.minutes and |
|
||||||
self.seconds == other.seconds and |
|
||||||
self.microseconds == other.microseconds and |
|
||||||
self.leapdays == other.leapdays and |
|
||||||
self.year == other.year and |
|
||||||
self.month == other.month and |
|
||||||
self.day == other.day and |
|
||||||
self.hour == other.hour and |
|
||||||
self.minute == other.minute and |
|
||||||
self.second == other.second and |
|
||||||
self.microsecond == other.microsecond) |
|
||||||
|
|
||||||
def __hash__(self): |
|
||||||
return hash(( |
|
||||||
self.weekday, |
|
||||||
self.years, |
|
||||||
self.months, |
|
||||||
self.days, |
|
||||||
self.hours, |
|
||||||
self.minutes, |
|
||||||
self.seconds, |
|
||||||
self.microseconds, |
|
||||||
self.leapdays, |
|
||||||
self.year, |
|
||||||
self.month, |
|
||||||
self.day, |
|
||||||
self.hour, |
|
||||||
self.minute, |
|
||||||
self.second, |
|
||||||
self.microsecond, |
|
||||||
)) |
|
||||||
|
|
||||||
def __ne__(self, other): |
|
||||||
return not self.__eq__(other) |
|
||||||
|
|
||||||
def __div__(self, other): |
|
||||||
try: |
|
||||||
reciprocal = 1 / float(other) |
|
||||||
except TypeError: |
|
||||||
return NotImplemented |
|
||||||
|
|
||||||
return self.__mul__(reciprocal) |
|
||||||
|
|
||||||
__truediv__ = __div__ |
|
||||||
|
|
||||||
def __repr__(self): |
|
||||||
l = [] |
|
||||||
for attr in ["years", "months", "days", "leapdays", |
|
||||||
"hours", "minutes", "seconds", "microseconds"]: |
|
||||||
value = getattr(self, attr) |
|
||||||
if value: |
|
||||||
l.append("{attr}={value:+g}".format(attr=attr, value=value)) |
|
||||||
for attr in ["year", "month", "day", "weekday", |
|
||||||
"hour", "minute", "second", "microsecond"]: |
|
||||||
value = getattr(self, attr) |
|
||||||
if value is not None: |
|
||||||
l.append("{attr}={value}".format(attr=attr, value=repr(value))) |
|
||||||
return "{classname}({attrs})".format(classname=self.__class__.__name__, |
|
||||||
attrs=", ".join(l)) |
|
||||||
|
|
||||||
|
|
||||||
def _sign(x): |
|
||||||
return int(copysign(1, x)) |
|
||||||
|
|
||||||
# vim:ts=4:sw=4:et |
|
File diff suppressed because it is too large
Load Diff
@ -1,12 +0,0 @@ |
|||||||
# -*- coding: utf-8 -*- |
|
||||||
from .tz import * |
|
||||||
from .tz import __doc__ |
|
||||||
|
|
||||||
__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange", |
|
||||||
"tzstr", "tzical", "tzwin", "tzwinlocal", "gettz", |
|
||||||
"enfold", "datetime_ambiguous", "datetime_exists", |
|
||||||
"resolve_imaginary", "UTC", "DeprecatedTzFormatWarning"] |
|
||||||
|
|
||||||
|
|
||||||
class DeprecatedTzFormatWarning(Warning): |
|
||||||
"""Warning raised when time zones are parsed from deprecated formats.""" |
|
@ -1,419 +0,0 @@ |
|||||||
from six import PY2 |
|
||||||
|
|
||||||
from functools import wraps |
|
||||||
|
|
||||||
from datetime import datetime, timedelta, tzinfo |
|
||||||
|
|
||||||
|
|
||||||
ZERO = timedelta(0) |
|
||||||
|
|
||||||
__all__ = ['tzname_in_python2', 'enfold'] |
|
||||||
|
|
||||||
|
|
||||||
def tzname_in_python2(namefunc): |
|
||||||
"""Change unicode output into bytestrings in Python 2 |
|
||||||
|
|
||||||
tzname() API changed in Python 3. It used to return bytes, but was changed |
|
||||||
to unicode strings |
|
||||||
""" |
|
||||||
if PY2: |
|
||||||
@wraps(namefunc) |
|
||||||
def adjust_encoding(*args, **kwargs): |
|
||||||
name = namefunc(*args, **kwargs) |
|
||||||
if name is not None: |
|
||||||
name = name.encode() |
|
||||||
|
|
||||||
return name |
|
||||||
|
|
||||||
return adjust_encoding |
|
||||||
else: |
|
||||||
return namefunc |
|
||||||
|
|
||||||
|
|
||||||
# The following is adapted from Alexander Belopolsky's tz library |
|
||||||
# https://github.com/abalkin/tz |
|
||||||
if hasattr(datetime, 'fold'): |
|
||||||
# This is the pre-python 3.6 fold situation |
|
||||||
def enfold(dt, fold=1): |
|
||||||
""" |
|
||||||
Provides a unified interface for assigning the ``fold`` attribute to |
|
||||||
datetimes both before and after the implementation of PEP-495. |
|
||||||
|
|
||||||
:param fold: |
|
||||||
The value for the ``fold`` attribute in the returned datetime. This |
|
||||||
should be either 0 or 1. |
|
||||||
|
|
||||||
:return: |
|
||||||
Returns an object for which ``getattr(dt, 'fold', 0)`` returns |
|
||||||
``fold`` for all versions of Python. In versions prior to |
|
||||||
Python 3.6, this is a ``_DatetimeWithFold`` object, which is a |
|
||||||
subclass of :py:class:`datetime.datetime` with the ``fold`` |
|
||||||
attribute added, if ``fold`` is 1. |
|
||||||
|
|
||||||
.. versionadded:: 2.6.0 |
|
||||||
""" |
|
||||||
return dt.replace(fold=fold) |
|
||||||
|
|
||||||
else: |
|
||||||
class _DatetimeWithFold(datetime): |
|
||||||
""" |
|
||||||
This is a class designed to provide a PEP 495-compliant interface for |
|
||||||
Python versions before 3.6. It is used only for dates in a fold, so |
|
||||||
the ``fold`` attribute is fixed at ``1``. |
|
||||||
|
|
||||||
.. versionadded:: 2.6.0 |
|
||||||
""" |
|
||||||
__slots__ = () |
|
||||||
|
|
||||||
def replace(self, *args, **kwargs): |
|
||||||
""" |
|
||||||
Return a datetime with the same attributes, except for those |
|
||||||
attributes given new values by whichever keyword arguments are |
|
||||||
specified. Note that tzinfo=None can be specified to create a naive |
|
||||||
datetime from an aware datetime with no conversion of date and time |
|
||||||
data. |
|
||||||
|
|
||||||
This is reimplemented in ``_DatetimeWithFold`` because pypy3 will |
|
||||||
return a ``datetime.datetime`` even if ``fold`` is unchanged. |
|
||||||
""" |
|
||||||
argnames = ( |
|
||||||
'year', 'month', 'day', 'hour', 'minute', 'second', |
|
||||||
'microsecond', 'tzinfo' |
|
||||||
) |
|
||||||
|
|
||||||
for arg, argname in zip(args, argnames): |
|
||||||
if argname in kwargs: |
|
||||||
raise TypeError('Duplicate argument: {}'.format(argname)) |
|
||||||
|
|
||||||
kwargs[argname] = arg |
|
||||||
|
|
||||||
for argname in argnames: |
|
||||||
if argname not in kwargs: |
|
||||||
kwargs[argname] = getattr(self, argname) |
|
||||||
|
|
||||||
dt_class = self.__class__ if kwargs.get('fold', 1) else datetime |
|
||||||
|
|
||||||
return dt_class(**kwargs) |
|
||||||
|
|
||||||
@property |
|
||||||
def fold(self): |
|
||||||
return 1 |
|
||||||
|
|
||||||
def enfold(dt, fold=1): |
|
||||||
""" |
|
||||||
Provides a unified interface for assigning the ``fold`` attribute to |
|
||||||
datetimes both before and after the implementation of PEP-495. |
|
||||||
|
|
||||||
:param fold: |
|
||||||
The value for the ``fold`` attribute in the returned datetime. This |
|
||||||
should be either 0 or 1. |
|
||||||
|
|
||||||
:return: |
|
||||||
Returns an object for which ``getattr(dt, 'fold', 0)`` returns |
|
||||||
``fold`` for all versions of Python. In versions prior to |
|
||||||
Python 3.6, this is a ``_DatetimeWithFold`` object, which is a |
|
||||||
subclass of :py:class:`datetime.datetime` with the ``fold`` |
|
||||||
attribute added, if ``fold`` is 1. |
|
||||||
|
|
||||||
.. versionadded:: 2.6.0 |
|
||||||
""" |
|
||||||
if getattr(dt, 'fold', 0) == fold: |
|
||||||
return dt |
|
||||||
|
|
||||||
args = dt.timetuple()[:6] |
|
||||||
args += (dt.microsecond, dt.tzinfo) |
|
||||||
|
|
||||||
if fold: |
|
||||||
return _DatetimeWithFold(*args) |
|
||||||
else: |
|
||||||
return datetime(*args) |
|
||||||
|
|
||||||
|
|
||||||
def _validate_fromutc_inputs(f): |
|
||||||
""" |
|
||||||
The CPython version of ``fromutc`` checks that the input is a ``datetime`` |
|
||||||
object and that ``self`` is attached as its ``tzinfo``. |
|
||||||
""" |
|
||||||
@wraps(f) |
|
||||||
def fromutc(self, dt): |
|
||||||
if not isinstance(dt, datetime): |
|
||||||
raise TypeError("fromutc() requires a datetime argument") |
|
||||||
if dt.tzinfo is not self: |
|
||||||
raise ValueError("dt.tzinfo is not self") |
|
||||||
|
|
||||||
return f(self, dt) |
|
||||||
|
|
||||||
return fromutc |
|
||||||
|
|
||||||
|
|
||||||
class _tzinfo(tzinfo): |
|
||||||
""" |
|
||||||
Base class for all ``dateutil`` ``tzinfo`` objects. |
|
||||||
""" |
|
||||||
|
|
||||||
def is_ambiguous(self, dt): |
|
||||||
""" |
|
||||||
Whether or not the "wall time" of a given datetime is ambiguous in this |
|
||||||
zone. |
|
||||||
|
|
||||||
:param dt: |
|
||||||
A :py:class:`datetime.datetime`, naive or time zone aware. |
|
||||||
|
|
||||||
|
|
||||||
:return: |
|
||||||
Returns ``True`` if ambiguous, ``False`` otherwise. |
|
||||||
|
|
||||||
.. versionadded:: 2.6.0 |
|
||||||
""" |
|
||||||
|
|
||||||
dt = dt.replace(tzinfo=self) |
|
||||||
|
|
||||||
wall_0 = enfold(dt, fold=0) |
|
||||||
wall_1 = enfold(dt, fold=1) |
|
||||||
|
|
||||||
same_offset = wall_0.utcoffset() == wall_1.utcoffset() |
|
||||||
same_dt = wall_0.replace(tzinfo=None) == wall_1.replace(tzinfo=None) |
|
||||||
|
|
||||||
return same_dt and not same_offset |
|
||||||
|
|
||||||
def _fold_status(self, dt_utc, dt_wall): |
|
||||||
""" |
|
||||||
Determine the fold status of a "wall" datetime, given a representation |
|
||||||
of the same datetime as a (naive) UTC datetime. This is calculated based |
|
||||||
on the assumption that ``dt.utcoffset() - dt.dst()`` is constant for all |
|
||||||
datetimes, and that this offset is the actual number of hours separating |
|
||||||
``dt_utc`` and ``dt_wall``. |
|
||||||
|
|
||||||
:param dt_utc: |
|
||||||
Representation of the datetime as UTC |
|
||||||
|
|
||||||
:param dt_wall: |
|
||||||
Representation of the datetime as "wall time". This parameter must |
|
||||||
either have a `fold` attribute or have a fold-naive |
|
||||||
:class:`datetime.tzinfo` attached, otherwise the calculation may |
|
||||||
fail. |
|
||||||
""" |
|
||||||
if self.is_ambiguous(dt_wall): |
|
||||||
delta_wall = dt_wall - dt_utc |
|
||||||
_fold = int(delta_wall == (dt_utc.utcoffset() - dt_utc.dst())) |
|
||||||
else: |
|
||||||
_fold = 0 |
|
||||||
|
|
||||||
return _fold |
|
||||||
|
|
||||||
def _fold(self, dt): |
|
||||||
return getattr(dt, 'fold', 0) |
|
||||||
|
|
||||||
def _fromutc(self, dt): |
|
||||||
""" |
|
||||||
Given a timezone-aware datetime in a given timezone, calculates a |
|
||||||
timezone-aware datetime in a new timezone. |
|
||||||
|
|
||||||
Since this is the one time that we *know* we have an unambiguous |
|
||||||
datetime object, we take this opportunity to determine whether the |
|
||||||
datetime is ambiguous and in a "fold" state (e.g. if it's the first |
|
||||||
occurrence, chronologically, of the ambiguous datetime). |
|
||||||
|
|
||||||
:param dt: |
|
||||||
A timezone-aware :class:`datetime.datetime` object. |
|
||||||
""" |
|
||||||
|
|
||||||
# Re-implement the algorithm from Python's datetime.py |
|
||||||
dtoff = dt.utcoffset() |
|
||||||
if dtoff is None: |
|
||||||
raise ValueError("fromutc() requires a non-None utcoffset() " |
|
||||||
"result") |
|
||||||
|
|
||||||
# The original datetime.py code assumes that `dst()` defaults to |
|
||||||
# zero during ambiguous times. PEP 495 inverts this presumption, so |
|
||||||
# for pre-PEP 495 versions of python, we need to tweak the algorithm. |
|
||||||
dtdst = dt.dst() |
|
||||||
if dtdst is None: |
|
||||||
raise ValueError("fromutc() requires a non-None dst() result") |
|
||||||
delta = dtoff - dtdst |
|
||||||
|
|
||||||
dt += delta |
|
||||||
# Set fold=1 so we can default to being in the fold for |
|
||||||
# ambiguous dates. |
|
||||||
dtdst = enfold(dt, fold=1).dst() |
|
||||||
if dtdst is None: |
|
||||||
raise ValueError("fromutc(): dt.dst gave inconsistent " |
|
||||||
"results; cannot convert") |
|
||||||
return dt + dtdst |
|
||||||
|
|
||||||
@_validate_fromutc_inputs |
|
||||||
def fromutc(self, dt): |
|
||||||
""" |
|
||||||
Given a timezone-aware datetime in a given timezone, calculates a |
|
||||||
timezone-aware datetime in a new timezone. |
|
||||||
|
|
||||||
Since this is the one time that we *know* we have an unambiguous |
|
||||||
datetime object, we take this opportunity to determine whether the |
|
||||||
datetime is ambiguous and in a "fold" state (e.g. if it's the first |
|
||||||
occurrence, chronologically, of the ambiguous datetime). |
|
||||||
|
|
||||||
:param dt: |
|
||||||
A timezone-aware :class:`datetime.datetime` object. |
|
||||||
""" |
|
||||||
dt_wall = self._fromutc(dt) |
|
||||||
|
|
||||||
# Calculate the fold status given the two datetimes. |
|
||||||
_fold = self._fold_status(dt, dt_wall) |
|
||||||
|
|
||||||
# Set the default fold value for ambiguous dates |
|
||||||
return enfold(dt_wall, fold=_fold) |
|
||||||
|
|
||||||
|
|
||||||
class tzrangebase(_tzinfo): |
|
||||||
""" |
|
||||||
This is an abstract base class for time zones represented by an annual |
|
||||||
transition into and out of DST. Child classes should implement the following |
|
||||||
methods: |
|
||||||
|
|
||||||
* ``__init__(self, *args, **kwargs)`` |
|
||||||
* ``transitions(self, year)`` - this is expected to return a tuple of |
|
||||||
datetimes representing the DST on and off transitions in standard |
|
||||||
time. |
|
||||||
|
|
||||||
A fully initialized ``tzrangebase`` subclass should also provide the |
|
||||||
following attributes: |
|
||||||
* ``hasdst``: Boolean whether or not the zone uses DST. |
|
||||||
* ``_dst_offset`` / ``_std_offset``: :class:`datetime.timedelta` objects |
|
||||||
representing the respective UTC offsets. |
|
||||||
* ``_dst_abbr`` / ``_std_abbr``: Strings representing the timezone short |
|
||||||
abbreviations in DST and STD, respectively. |
|
||||||
* ``_hasdst``: Whether or not the zone has DST. |
|
||||||
|
|
||||||
.. versionadded:: 2.6.0 |
|
||||||
""" |
|
||||||
def __init__(self): |
|
||||||
raise NotImplementedError('tzrangebase is an abstract base class') |
|
||||||
|
|
||||||
def utcoffset(self, dt): |
|
||||||
isdst = self._isdst(dt) |
|
||||||
|
|
||||||
if isdst is None: |
|
||||||
return None |
|
||||||
elif isdst: |
|
||||||
return self._dst_offset |
|
||||||
else: |
|
||||||
return self._std_offset |
|
||||||
|
|
||||||
def dst(self, dt): |
|
||||||
isdst = self._isdst(dt) |
|
||||||
|
|
||||||
if isdst is None: |
|
||||||
return None |
|
||||||
elif isdst: |
|
||||||
return self._dst_base_offset |
|
||||||
else: |
|
||||||
return ZERO |
|
||||||
|
|
||||||
@tzname_in_python2 |
|
||||||
def tzname(self, dt): |
|
||||||
if self._isdst(dt): |
|
||||||
return self._dst_abbr |
|
||||||
else: |
|
||||||
return self._std_abbr |
|
||||||
|
|
||||||
def fromutc(self, dt): |
|
||||||
""" Given a datetime in UTC, return local time """ |
|
||||||
if not isinstance(dt, datetime): |
|
||||||
raise TypeError("fromutc() requires a datetime argument") |
|
||||||
|
|
||||||
if dt.tzinfo is not self: |
|
||||||
raise ValueError("dt.tzinfo is not self") |
|
||||||
|
|
||||||
# Get transitions - if there are none, fixed offset |
|
||||||
transitions = self.transitions(dt.year) |
|
||||||
if transitions is None: |
|
||||||
return dt + self.utcoffset(dt) |
|
||||||
|
|
||||||
# Get the transition times in UTC |
|
||||||
dston, dstoff = transitions |
|
||||||
|
|
||||||
dston -= self._std_offset |
|
||||||
dstoff -= self._std_offset |
|
||||||
|
|
||||||
utc_transitions = (dston, dstoff) |
|
||||||
dt_utc = dt.replace(tzinfo=None) |
|
||||||
|
|
||||||
isdst = self._naive_isdst(dt_utc, utc_transitions) |
|
||||||
|
|
||||||
if isdst: |
|
||||||
dt_wall = dt + self._dst_offset |
|
||||||
else: |
|
||||||
dt_wall = dt + self._std_offset |
|
||||||
|
|
||||||
_fold = int(not isdst and self.is_ambiguous(dt_wall)) |
|
||||||
|
|
||||||
return enfold(dt_wall, fold=_fold) |
|
||||||
|
|
||||||
def is_ambiguous(self, dt): |
|
||||||
""" |
|
||||||
Whether or not the "wall time" of a given datetime is ambiguous in this |
|
||||||
zone. |
|
||||||
|
|
||||||
:param dt: |
|
||||||
A :py:class:`datetime.datetime`, naive or time zone aware. |
|
||||||
|
|
||||||
|
|
||||||
:return: |
|
||||||
Returns ``True`` if ambiguous, ``False`` otherwise. |
|
||||||
|
|
||||||
.. versionadded:: 2.6.0 |
|
||||||
""" |
|
||||||
if not self.hasdst: |
|
||||||
return False |
|
||||||
|
|
||||||
start, end = self.transitions(dt.year) |
|
||||||
|
|
||||||
dt = dt.replace(tzinfo=None) |
|
||||||
return (end <= dt < end + self._dst_base_offset) |
|
||||||
|
|
||||||
def _isdst(self, dt): |
|
||||||
if not self.hasdst: |
|
||||||
return False |
|
||||||
elif dt is None: |
|
||||||
return None |
|
||||||
|
|
||||||
transitions = self.transitions(dt.year) |
|
||||||
|
|
||||||
if transitions is None: |
|
||||||
return False |
|
||||||
|
|
||||||
dt = dt.replace(tzinfo=None) |
|
||||||
|
|
||||||
isdst = self._naive_isdst(dt, transitions) |
|
||||||
|
|
||||||
# Handle ambiguous dates |
|
||||||
if not isdst and self.is_ambiguous(dt): |
|
||||||
return not self._fold(dt) |
|
||||||
else: |
|
||||||
return isdst |
|
||||||
|
|
||||||
def _naive_isdst(self, dt, transitions): |
|
||||||
dston, dstoff = transitions |
|
||||||
|
|
||||||
dt = dt.replace(tzinfo=None) |
|
||||||
|
|
||||||
if dston < dstoff: |
|
||||||
isdst = dston <= dt < dstoff |
|
||||||
else: |
|
||||||
isdst = not dstoff <= dt < dston |
|
||||||
|
|
||||||
return isdst |
|
||||||
|
|
||||||
@property |
|
||||||
def _dst_base_offset(self): |
|
||||||
return self._dst_offset - self._std_offset |
|
||||||
|
|
||||||
__hash__ = None |
|
||||||
|
|
||||||
def __ne__(self, other): |
|
||||||
return not (self == other) |
|
||||||
|
|
||||||
def __repr__(self): |
|
||||||
return "%s(...)" % self.__class__.__name__ |
|
||||||
|
|
||||||
__reduce__ = object.__reduce__ |
|
@ -1,80 +0,0 @@ |
|||||||
from datetime import timedelta |
|
||||||
import weakref |
|
||||||
from collections import OrderedDict |
|
||||||
|
|
||||||
from six.moves import _thread |
|
||||||
|
|
||||||
|
|
||||||
class _TzSingleton(type): |
|
||||||
def __init__(cls, *args, **kwargs): |
|
||||||
cls.__instance = None |
|
||||||
super(_TzSingleton, cls).__init__(*args, **kwargs) |
|
||||||
|
|
||||||
def __call__(cls): |
|
||||||
if cls.__instance is None: |
|
||||||
cls.__instance = super(_TzSingleton, cls).__call__() |
|
||||||
return cls.__instance |
|
||||||
|
|
||||||
|
|
||||||
class _TzFactory(type): |
|
||||||
def instance(cls, *args, **kwargs): |
|
||||||
"""Alternate constructor that returns a fresh instance""" |
|
||||||
return type.__call__(cls, *args, **kwargs) |
|
||||||
|
|
||||||
|
|
||||||
class _TzOffsetFactory(_TzFactory): |
|
||||||
def __init__(cls, *args, **kwargs): |
|
||||||
cls.__instances = weakref.WeakValueDictionary() |
|
||||||
cls.__strong_cache = OrderedDict() |
|
||||||
cls.__strong_cache_size = 8 |
|
||||||
|
|
||||||
cls._cache_lock = _thread.allocate_lock() |
|
||||||
|
|
||||||
def __call__(cls, name, offset): |
|
||||||
if isinstance(offset, timedelta): |
|
||||||
key = (name, offset.total_seconds()) |
|
||||||
else: |
|
||||||
key = (name, offset) |
|
||||||
|
|
||||||
instance = cls.__instances.get(key, None) |
|
||||||
if instance is None: |
|
||||||
instance = cls.__instances.setdefault(key, |
|
||||||
cls.instance(name, offset)) |
|
||||||
|
|
||||||
# This lock may not be necessary in Python 3. See GH issue #901 |
|
||||||
with cls._cache_lock: |
|
||||||
cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance) |
|
||||||
|
|
||||||
# Remove an item if the strong cache is overpopulated |
|
||||||
if len(cls.__strong_cache) > cls.__strong_cache_size: |
|
||||||
cls.__strong_cache.popitem(last=False) |
|
||||||
|
|
||||||
return instance |
|
||||||
|
|
||||||
|
|
||||||
class _TzStrFactory(_TzFactory): |
|
||||||
def __init__(cls, *args, **kwargs): |
|
||||||
cls.__instances = weakref.WeakValueDictionary() |
|
||||||
cls.__strong_cache = OrderedDict() |
|
||||||
cls.__strong_cache_size = 8 |
|
||||||
|
|
||||||
cls.__cache_lock = _thread.allocate_lock() |
|
||||||
|
|
||||||
def __call__(cls, s, posix_offset=False): |
|
||||||
key = (s, posix_offset) |
|
||||||
instance = cls.__instances.get(key, None) |
|
||||||
|
|
||||||
if instance is None: |
|
||||||
instance = cls.__instances.setdefault(key, |
|
||||||
cls.instance(s, posix_offset)) |
|
||||||
|
|
||||||
# This lock may not be necessary in Python 3. See GH issue #901 |
|
||||||
with cls.__cache_lock: |
|
||||||
cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance) |
|
||||||
|
|
||||||
# Remove an item if the strong cache is overpopulated |
|
||||||
if len(cls.__strong_cache) > cls.__strong_cache_size: |
|
||||||
cls.__strong_cache.popitem(last=False) |
|
||||||
|
|
||||||
return instance |
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -1,370 +0,0 @@ |
|||||||
# -*- coding: utf-8 -*- |
|
||||||
""" |
|
||||||
This module provides an interface to the native time zone data on Windows, |
|
||||||
including :py:class:`datetime.tzinfo` implementations. |
|
||||||
|
|
||||||
Attempting to import this module on a non-Windows platform will raise an |
|
||||||
:py:obj:`ImportError`. |
|
||||||
""" |
|
||||||
# This code was originally contributed by Jeffrey Harris. |
|
||||||
import datetime |
|
||||||
import struct |
|
||||||
|
|
||||||
from six.moves import winreg |
|
||||||
from six import text_type |
|
||||||
|
|
||||||
try: |
|
||||||
import ctypes |
|
||||||
from ctypes import wintypes |
|
||||||
except ValueError: |
|
||||||
# ValueError is raised on non-Windows systems for some horrible reason. |
|
||||||
raise ImportError("Running tzwin on non-Windows system") |
|
||||||
|
|
||||||
from ._common import tzrangebase |
|
||||||
|
|
||||||
__all__ = ["tzwin", "tzwinlocal", "tzres"] |
|
||||||
|
|
||||||
ONEWEEK = datetime.timedelta(7) |
|
||||||
|
|
||||||
TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones" |
|
||||||
TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones" |
|
||||||
TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation" |
|
||||||
|
|
||||||
|
|
||||||
def _settzkeyname(): |
|
||||||
handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) |
|
||||||
try: |
|
||||||
winreg.OpenKey(handle, TZKEYNAMENT).Close() |
|
||||||
TZKEYNAME = TZKEYNAMENT |
|
||||||
except WindowsError: |
|
||||||
TZKEYNAME = TZKEYNAME9X |
|
||||||
handle.Close() |
|
||||||
return TZKEYNAME |
|
||||||
|
|
||||||
|
|
||||||
TZKEYNAME = _settzkeyname() |
|
||||||
|
|
||||||
|
|
||||||
class tzres(object): |
|
||||||
""" |
|
||||||
Class for accessing ``tzres.dll``, which contains timezone name related |
|
||||||
resources. |
|
||||||
|
|
||||||
.. versionadded:: 2.5.0 |
|
||||||
""" |
|
||||||
p_wchar = ctypes.POINTER(wintypes.WCHAR) # Pointer to a wide char |
|
||||||
|
|
||||||
def __init__(self, tzres_loc='tzres.dll'): |
|
||||||
# Load the user32 DLL so we can load strings from tzres |
|
||||||
user32 = ctypes.WinDLL('user32') |
|
||||||
|
|
||||||
# Specify the LoadStringW function |
|
||||||
user32.LoadStringW.argtypes = (wintypes.HINSTANCE, |
|
||||||
wintypes.UINT, |
|
||||||
wintypes.LPWSTR, |
|
||||||
ctypes.c_int) |
|
||||||
|
|
||||||
self.LoadStringW = user32.LoadStringW |
|
||||||
self._tzres = ctypes.WinDLL(tzres_loc) |
|
||||||
self.tzres_loc = tzres_loc |
|
||||||
|
|
||||||
def load_name(self, offset): |
|
||||||
""" |
|
||||||
Load a timezone name from a DLL offset (integer). |
|
||||||
|
|
||||||
>>> from dateutil.tzwin import tzres |
|
||||||
>>> tzr = tzres() |
|
||||||
>>> print(tzr.load_name(112)) |
|
||||||
'Eastern Standard Time' |
|
||||||
|
|
||||||
:param offset: |
|
||||||
A positive integer value referring to a string from the tzres dll. |
|
||||||
|
|
||||||
.. note:: |
|
||||||
|
|
||||||
Offsets found in the registry are generally of the form |
|
||||||
``@tzres.dll,-114``. The offset in this case is 114, not -114. |
|
||||||
|
|
||||||
""" |
|
||||||
resource = self.p_wchar() |
|
||||||
lpBuffer = ctypes.cast(ctypes.byref(resource), wintypes.LPWSTR) |
|
||||||
nchar = self.LoadStringW(self._tzres._handle, offset, lpBuffer, 0) |
|
||||||
return resource[:nchar] |
|
||||||
|
|
||||||
def name_from_string(self, tzname_str): |
|
||||||
""" |
|
||||||
Parse strings as returned from the Windows registry into the time zone |
|
||||||
name as defined in the registry. |
|
||||||
|
|
||||||
>>> from dateutil.tzwin import tzres |
|
||||||
>>> tzr = tzres() |
|
||||||
>>> print(tzr.name_from_string('@tzres.dll,-251')) |
|
||||||
'Dateline Daylight Time' |
|
||||||
>>> print(tzr.name_from_string('Eastern Standard Time')) |
|
||||||
'Eastern Standard Time' |
|
||||||
|
|
||||||
:param tzname_str: |
|
||||||
A timezone name string as returned from a Windows registry key. |
|
||||||
|
|
||||||
:return: |
|
||||||
Returns the localized timezone string from tzres.dll if the string |
|
||||||
is of the form `@tzres.dll,-offset`, else returns the input string. |
|
||||||
""" |
|
||||||
if not tzname_str.startswith('@'): |
|
||||||
return tzname_str |
|
||||||
|
|
||||||
name_splt = tzname_str.split(',-') |
|
||||||
try: |
|
||||||
offset = int(name_splt[1]) |
|
||||||
except: |
|
||||||
raise ValueError("Malformed timezone string.") |
|
||||||
|
|
||||||
return self.load_name(offset) |
|
||||||
|
|
||||||
|
|
||||||
class tzwinbase(tzrangebase): |
|
||||||
"""tzinfo class based on win32's timezones available in the registry.""" |
|
||||||
def __init__(self): |
|
||||||
raise NotImplementedError('tzwinbase is an abstract base class') |
|
||||||
|
|
||||||
def __eq__(self, other): |
|
||||||
# Compare on all relevant dimensions, including name. |
|
||||||
if not isinstance(other, tzwinbase): |
|
||||||
return NotImplemented |
|
||||||
|
|
||||||
return (self._std_offset == other._std_offset and |
|
||||||
self._dst_offset == other._dst_offset and |
|
||||||
self._stddayofweek == other._stddayofweek and |
|
||||||
self._dstdayofweek == other._dstdayofweek and |
|
||||||
self._stdweeknumber == other._stdweeknumber and |
|
||||||
self._dstweeknumber == other._dstweeknumber and |
|
||||||
self._stdhour == other._stdhour and |
|
||||||
self._dsthour == other._dsthour and |
|
||||||
self._stdminute == other._stdminute and |
|
||||||
self._dstminute == other._dstminute and |
|
||||||
self._std_abbr == other._std_abbr and |
|
||||||
self._dst_abbr == other._dst_abbr) |
|
||||||
|
|
||||||
@staticmethod |
|
||||||
def list(): |
|
||||||
"""Return a list of all time zones known to the system.""" |
|
||||||
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: |
|
||||||
with winreg.OpenKey(handle, TZKEYNAME) as tzkey: |
|
||||||
result = [winreg.EnumKey(tzkey, i) |
|
||||||
for i in range(winreg.QueryInfoKey(tzkey)[0])] |
|
||||||
return result |
|
||||||
|
|
||||||
def display(self): |
|
||||||
""" |
|
||||||
Return the display name of the time zone. |
|
||||||
""" |
|
||||||
return self._display |
|
||||||
|
|
||||||
def transitions(self, year): |
|
||||||
""" |
|
||||||
For a given year, get the DST on and off transition times, expressed |
|
||||||
always on the standard time side. For zones with no transitions, this |
|
||||||
function returns ``None``. |
|
||||||
|
|
||||||
:param year: |
|
||||||
The year whose transitions you would like to query. |
|
||||||
|
|
||||||
:return: |
|
||||||
Returns a :class:`tuple` of :class:`datetime.datetime` objects, |
|
||||||
``(dston, dstoff)`` for zones with an annual DST transition, or |
|
||||||
``None`` for fixed offset zones. |
|
||||||
""" |
|
||||||
|
|
||||||
if not self.hasdst: |
|
||||||
return None |
|
||||||
|
|
||||||
dston = picknthweekday(year, self._dstmonth, self._dstdayofweek, |
|
||||||
self._dsthour, self._dstminute, |
|
||||||
self._dstweeknumber) |
|
||||||
|
|
||||||
dstoff = picknthweekday(year, self._stdmonth, self._stddayofweek, |
|
||||||
self._stdhour, self._stdminute, |
|
||||||
self._stdweeknumber) |
|
||||||
|
|
||||||
# Ambiguous dates default to the STD side |
|
||||||
dstoff -= self._dst_base_offset |
|
||||||
|
|
||||||
return dston, dstoff |
|
||||||
|
|
||||||
def _get_hasdst(self): |
|
||||||
return self._dstmonth != 0 |
|
||||||
|
|
||||||
@property |
|
||||||
def _dst_base_offset(self): |
|
||||||
return self._dst_base_offset_ |
|
||||||
|
|
||||||
|
|
||||||
class tzwin(tzwinbase): |
|
||||||
""" |
|
||||||
Time zone object created from the zone info in the Windows registry |
|
||||||
|
|
||||||
These are similar to :py:class:`dateutil.tz.tzrange` objects in that |
|
||||||
the time zone data is provided in the format of a single offset rule |
|
||||||
for either 0 or 2 time zone transitions per year. |
|
||||||
|
|
||||||
:param: name |
|
||||||
The name of a Windows time zone key, e.g. "Eastern Standard Time". |
|
||||||
The full list of keys can be retrieved with :func:`tzwin.list`. |
|
||||||
""" |
|
||||||
|
|
||||||
def __init__(self, name): |
|
||||||
self._name = name |
|
||||||
|
|
||||||
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: |
|
||||||
tzkeyname = text_type("{kn}\\{name}").format(kn=TZKEYNAME, name=name) |
|
||||||
with winreg.OpenKey(handle, tzkeyname) as tzkey: |
|
||||||
keydict = valuestodict(tzkey) |
|
||||||
|
|
||||||
self._std_abbr = keydict["Std"] |
|
||||||
self._dst_abbr = keydict["Dlt"] |
|
||||||
|
|
||||||
self._display = keydict["Display"] |
|
||||||
|
|
||||||
# See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm |
|
||||||
tup = struct.unpack("=3l16h", keydict["TZI"]) |
|
||||||
stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1 |
|
||||||
dstoffset = stdoffset-tup[2] # + DaylightBias * -1 |
|
||||||
self._std_offset = datetime.timedelta(minutes=stdoffset) |
|
||||||
self._dst_offset = datetime.timedelta(minutes=dstoffset) |
|
||||||
|
|
||||||
# for the meaning see the win32 TIME_ZONE_INFORMATION structure docs |
|
||||||
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx |
|
||||||
(self._stdmonth, |
|
||||||
self._stddayofweek, # Sunday = 0 |
|
||||||
self._stdweeknumber, # Last = 5 |
|
||||||
self._stdhour, |
|
||||||
self._stdminute) = tup[4:9] |
|
||||||
|
|
||||||
(self._dstmonth, |
|
||||||
self._dstdayofweek, # Sunday = 0 |
|
||||||
self._dstweeknumber, # Last = 5 |
|
||||||
self._dsthour, |
|
||||||
self._dstminute) = tup[12:17] |
|
||||||
|
|
||||||
self._dst_base_offset_ = self._dst_offset - self._std_offset |
|
||||||
self.hasdst = self._get_hasdst() |
|
||||||
|
|
||||||
def __repr__(self): |
|
||||||
return "tzwin(%s)" % repr(self._name) |
|
||||||
|
|
||||||
def __reduce__(self): |
|
||||||
return (self.__class__, (self._name,)) |
|
||||||
|
|
||||||
|
|
||||||
class tzwinlocal(tzwinbase): |
|
||||||
""" |
|
||||||
Class representing the local time zone information in the Windows registry |
|
||||||
|
|
||||||
While :class:`dateutil.tz.tzlocal` makes system calls (via the :mod:`time` |
|
||||||
module) to retrieve time zone information, ``tzwinlocal`` retrieves the |
|
||||||
rules directly from the Windows registry and creates an object like |
|
||||||
:class:`dateutil.tz.tzwin`. |
|
||||||
|
|
||||||
Because Windows does not have an equivalent of :func:`time.tzset`, on |
|
||||||
Windows, :class:`dateutil.tz.tzlocal` instances will always reflect the |
|
||||||
time zone settings *at the time that the process was started*, meaning |
|
||||||
changes to the machine's time zone settings during the run of a program |
|
||||||
on Windows will **not** be reflected by :class:`dateutil.tz.tzlocal`. |
|
||||||
Because ``tzwinlocal`` reads the registry directly, it is unaffected by |
|
||||||
this issue. |
|
||||||
""" |
|
||||||
def __init__(self): |
|
||||||
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: |
|
||||||
with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey: |
|
||||||
keydict = valuestodict(tzlocalkey) |
|
||||||
|
|
||||||
self._std_abbr = keydict["StandardName"] |
|
||||||
self._dst_abbr = keydict["DaylightName"] |
|
||||||
|
|
||||||
try: |
|
||||||
tzkeyname = text_type('{kn}\\{sn}').format(kn=TZKEYNAME, |
|
||||||
sn=self._std_abbr) |
|
||||||
with winreg.OpenKey(handle, tzkeyname) as tzkey: |
|
||||||
_keydict = valuestodict(tzkey) |
|
||||||
self._display = _keydict["Display"] |
|
||||||
except OSError: |
|
||||||
self._display = None |
|
||||||
|
|
||||||
stdoffset = -keydict["Bias"]-keydict["StandardBias"] |
|
||||||
dstoffset = stdoffset-keydict["DaylightBias"] |
|
||||||
|
|
||||||
self._std_offset = datetime.timedelta(minutes=stdoffset) |
|
||||||
self._dst_offset = datetime.timedelta(minutes=dstoffset) |
|
||||||
|
|
||||||
# For reasons unclear, in this particular key, the day of week has been |
|
||||||
# moved to the END of the SYSTEMTIME structure. |
|
||||||
tup = struct.unpack("=8h", keydict["StandardStart"]) |
|
||||||
|
|
||||||
(self._stdmonth, |
|
||||||
self._stdweeknumber, # Last = 5 |
|
||||||
self._stdhour, |
|
||||||
self._stdminute) = tup[1:5] |
|
||||||
|
|
||||||
self._stddayofweek = tup[7] |
|
||||||
|
|
||||||
tup = struct.unpack("=8h", keydict["DaylightStart"]) |
|
||||||
|
|
||||||
(self._dstmonth, |
|
||||||
self._dstweeknumber, # Last = 5 |
|
||||||
self._dsthour, |
|
||||||
self._dstminute) = tup[1:5] |
|
||||||
|
|
||||||
self._dstdayofweek = tup[7] |
|
||||||
|
|
||||||
self._dst_base_offset_ = self._dst_offset - self._std_offset |
|
||||||
self.hasdst = self._get_hasdst() |
|
||||||
|
|
||||||
def __repr__(self): |
|
||||||
return "tzwinlocal()" |
|
||||||
|
|
||||||
def __str__(self): |
|
||||||
# str will return the standard name, not the daylight name. |
|
||||||
return "tzwinlocal(%s)" % repr(self._std_abbr) |
|
||||||
|
|
||||||
def __reduce__(self): |
|
||||||
return (self.__class__, ()) |
|
||||||
|
|
||||||
|
|
||||||
def picknthweekday(year, month, dayofweek, hour, minute, whichweek): |
|
||||||
""" dayofweek == 0 means Sunday, whichweek 5 means last instance """ |
|
||||||
first = datetime.datetime(year, month, 1, hour, minute) |
|
||||||
|
|
||||||
# This will work if dayofweek is ISO weekday (1-7) or Microsoft-style (0-6), |
|
||||||
# Because 7 % 7 = 0 |
|
||||||
weekdayone = first.replace(day=((dayofweek - first.isoweekday()) % 7) + 1) |
|
||||||
wd = weekdayone + ((whichweek - 1) * ONEWEEK) |
|
||||||
if (wd.month != month): |
|
||||||
wd -= ONEWEEK |
|
||||||
|
|
||||||
return wd |
|
||||||
|
|
||||||
|
|
||||||
def valuestodict(key): |
|
||||||
"""Convert a registry key's values to a dictionary.""" |
|
||||||
dout = {} |
|
||||||
size = winreg.QueryInfoKey(key)[1] |
|
||||||
tz_res = None |
|
||||||
|
|
||||||
for i in range(size): |
|
||||||
key_name, value, dtype = winreg.EnumValue(key, i) |
|
||||||
if dtype == winreg.REG_DWORD or dtype == winreg.REG_DWORD_LITTLE_ENDIAN: |
|
||||||
# If it's a DWORD (32-bit integer), it's stored as unsigned - convert |
|
||||||
# that to a proper signed integer |
|
||||||
if value & (1 << 31): |
|
||||||
value = value - (1 << 32) |
|
||||||
elif dtype == winreg.REG_SZ: |
|
||||||
# If it's a reference to the tzres DLL, load the actual string |
|
||||||
if value.startswith('@tzres'): |
|
||||||
tz_res = tz_res or tzres() |
|
||||||
value = tz_res.name_from_string(value) |
|
||||||
|
|
||||||
value = value.rstrip('\x00') # Remove trailing nulls |
|
||||||
|
|
||||||
dout[key_name] = value |
|
||||||
|
|
||||||
return dout |
|
@ -1,2 +0,0 @@ |
|||||||
# tzwin has moved to dateutil.tz.win |
|
||||||
from .tz.win import * |
|
@ -1,71 +0,0 @@ |
|||||||
# -*- coding: utf-8 -*- |
|
||||||
""" |
|
||||||
This module offers general convenience and utility functions for dealing with |
|
||||||
datetimes. |
|
||||||
|
|
||||||
.. versionadded:: 2.7.0 |
|
||||||
""" |
|
||||||
from __future__ import unicode_literals |
|
||||||
|
|
||||||
from datetime import datetime, time |
|
||||||
|
|
||||||
|
|
||||||
def today(tzinfo=None): |
|
||||||
""" |
|
||||||
Returns a :py:class:`datetime` representing the current day at midnight |
|
||||||
|
|
||||||
:param tzinfo: |
|
||||||
The time zone to attach (also used to determine the current day). |
|
||||||
|
|
||||||
:return: |
|
||||||
A :py:class:`datetime.datetime` object representing the current day |
|
||||||
at midnight. |
|
||||||
""" |
|
||||||
|
|
||||||
dt = datetime.now(tzinfo) |
|
||||||
return datetime.combine(dt.date(), time(0, tzinfo=tzinfo)) |
|
||||||
|
|
||||||
|
|
||||||
def default_tzinfo(dt, tzinfo): |
|
||||||
""" |
|
||||||
Sets the ``tzinfo`` parameter on naive datetimes only |
|
||||||
|
|
||||||
This is useful for example when you are provided a datetime that may have |
|
||||||
either an implicit or explicit time zone, such as when parsing a time zone |
|
||||||
string. |
|
||||||
|
|
||||||
.. doctest:: |
|
||||||
|
|
||||||
>>> from dateutil.tz import tzoffset |
|
||||||
>>> from dateutil.parser import parse |
|
||||||
>>> from dateutil.utils import default_tzinfo |
|
||||||
>>> dflt_tz = tzoffset("EST", -18000) |
|
||||||
>>> print(default_tzinfo(parse('2014-01-01 12:30 UTC'), dflt_tz)) |
|
||||||
2014-01-01 12:30:00+00:00 |
|
||||||
>>> print(default_tzinfo(parse('2014-01-01 12:30'), dflt_tz)) |
|
||||||
2014-01-01 12:30:00-05:00 |
|
||||||
|
|
||||||
:param dt: |
|
||||||
The datetime on which to replace the time zone |
|
||||||
|
|
||||||
:param tzinfo: |
|
||||||
The :py:class:`datetime.tzinfo` subclass instance to assign to |
|
||||||
``dt`` if (and only if) it is naive. |
|
||||||
|
|
||||||
:return: |
|
||||||
Returns an aware :py:class:`datetime.datetime`. |
|
||||||
""" |
|
||||||
if dt.tzinfo is not None: |
|
||||||
return dt |
|
||||||
else: |
|
||||||
return dt.replace(tzinfo=tzinfo) |
|
||||||
|
|
||||||
|
|
||||||
def within_delta(dt1, dt2, delta): |
|
||||||
""" |
|
||||||
Useful for comparing two datetimes that may a negilible difference |
|
||||||
to be considered equal. |
|
||||||
""" |
|
||||||
delta = abs(delta) |
|
||||||
difference = dt1 - dt2 |
|
||||||
return -delta <= difference <= delta |
|
@ -1,167 +0,0 @@ |
|||||||
# -*- coding: utf-8 -*- |
|
||||||
import warnings |
|
||||||
import json |
|
||||||
|
|
||||||
from tarfile import TarFile |
|
||||||
from pkgutil import get_data |
|
||||||
from io import BytesIO |
|
||||||
|
|
||||||
from dateutil.tz import tzfile as _tzfile |
|
||||||
|
|
||||||
__all__ = ["get_zonefile_instance", "gettz", "gettz_db_metadata"] |
|
||||||
|
|
||||||
ZONEFILENAME = "dateutil-zoneinfo.tar.gz" |
|
||||||
METADATA_FN = 'METADATA' |
|
||||||
|
|
||||||
|
|
||||||
class tzfile(_tzfile): |
|
||||||
def __reduce__(self): |
|
||||||
return (gettz, (self._filename,)) |
|
||||||
|
|
||||||
|
|
||||||
def getzoneinfofile_stream(): |
|
||||||
try: |
|
||||||
return BytesIO(get_data(__name__, ZONEFILENAME)) |
|
||||||
except IOError as e: # TODO switch to FileNotFoundError? |
|
||||||
warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror)) |
|
||||||
return None |
|
||||||
|
|
||||||
|
|
||||||
class ZoneInfoFile(object): |
|
||||||
def __init__(self, zonefile_stream=None): |
|
||||||
if zonefile_stream is not None: |
|
||||||
with TarFile.open(fileobj=zonefile_stream) as tf: |
|
||||||
self.zones = {zf.name: tzfile(tf.extractfile(zf), filename=zf.name) |
|
||||||
for zf in tf.getmembers() |
|
||||||
if zf.isfile() and zf.name != METADATA_FN} |
|
||||||
# deal with links: They'll point to their parent object. Less |
|
||||||
# waste of memory |
|
||||||
links = {zl.name: self.zones[zl.linkname] |
|
||||||
for zl in tf.getmembers() if |
|
||||||
zl.islnk() or zl.issym()} |
|
||||||
self.zones.update(links) |
|
||||||
try: |
|
||||||
metadata_json = tf.extractfile(tf.getmember(METADATA_FN)) |
|
||||||
metadata_str = metadata_json.read().decode('UTF-8') |
|
||||||
self.metadata = json.loads(metadata_str) |
|
||||||
except KeyError: |
|
||||||
# no metadata in tar file |
|
||||||
self.metadata = None |
|
||||||
else: |
|
||||||
self.zones = {} |
|
||||||
self.metadata = None |
|
||||||
|
|
||||||
def get(self, name, default=None): |
|
||||||
""" |
|
||||||
Wrapper for :func:`ZoneInfoFile.zones.get`. This is a convenience method |
|
||||||
for retrieving zones from the zone dictionary. |
|
||||||
|
|
||||||
:param name: |
|
||||||
The name of the zone to retrieve. (Generally IANA zone names) |
|
||||||
|
|
||||||
:param default: |
|
||||||
The value to return in the event of a missing key. |
|
||||||
|
|
||||||
.. versionadded:: 2.6.0 |
|
||||||
|
|
||||||
""" |
|
||||||
return self.zones.get(name, default) |
|
||||||
|
|
||||||
|
|
||||||
# The current API has gettz as a module function, although in fact it taps into |
|
||||||
# a stateful class. So as a workaround for now, without changing the API, we |
|
||||||
# will create a new "global" class instance the first time a user requests a |
|
||||||
# timezone. Ugly, but adheres to the api. |
|
||||||
# |
|
||||||
# TODO: Remove after deprecation period. |
|
||||||
_CLASS_ZONE_INSTANCE = [] |
|
||||||
|
|
||||||
|
|
||||||
def get_zonefile_instance(new_instance=False): |
|
||||||
""" |
|
||||||
This is a convenience function which provides a :class:`ZoneInfoFile` |
|
||||||
instance using the data provided by the ``dateutil`` package. By default, it |
|
||||||
caches a single instance of the ZoneInfoFile object and returns that. |
|
||||||
|
|
||||||
:param new_instance: |
|
||||||
If ``True``, a new instance of :class:`ZoneInfoFile` is instantiated and |
|
||||||
used as the cached instance for the next call. Otherwise, new instances |
|
||||||
are created only as necessary. |
|
||||||
|
|
||||||
:return: |
|
||||||
Returns a :class:`ZoneInfoFile` object. |
|
||||||
|
|
||||||
.. versionadded:: 2.6 |
|
||||||
""" |
|
||||||
if new_instance: |
|
||||||
zif = None |
|
||||||
else: |
|
||||||
zif = getattr(get_zonefile_instance, '_cached_instance', None) |
|
||||||
|
|
||||||
if zif is None: |
|
||||||
zif = ZoneInfoFile(getzoneinfofile_stream()) |
|
||||||
|
|
||||||
get_zonefile_instance._cached_instance = zif |
|
||||||
|
|
||||||
return zif |
|
||||||
|
|
||||||
|
|
||||||
def gettz(name): |
|
||||||
""" |
|
||||||
This retrieves a time zone from the local zoneinfo tarball that is packaged |
|
||||||
with dateutil. |
|
||||||
|
|
||||||
:param name: |
|
||||||
An IANA-style time zone name, as found in the zoneinfo file. |
|
||||||
|
|
||||||
:return: |
|
||||||
Returns a :class:`dateutil.tz.tzfile` time zone object. |
|
||||||
|
|
||||||
.. warning:: |
|
||||||
It is generally inadvisable to use this function, and it is only |
|
||||||
provided for API compatibility with earlier versions. This is *not* |
|
||||||
equivalent to ``dateutil.tz.gettz()``, which selects an appropriate |
|
||||||
time zone based on the inputs, favoring system zoneinfo. This is ONLY |
|
||||||
for accessing the dateutil-specific zoneinfo (which may be out of |
|
||||||
date compared to the system zoneinfo). |
|
||||||
|
|
||||||
.. deprecated:: 2.6 |
|
||||||
If you need to use a specific zoneinfofile over the system zoneinfo, |
|
||||||
instantiate a :class:`dateutil.zoneinfo.ZoneInfoFile` object and call |
|
||||||
:func:`dateutil.zoneinfo.ZoneInfoFile.get(name)` instead. |
|
||||||
|
|
||||||
Use :func:`get_zonefile_instance` to retrieve an instance of the |
|
||||||
dateutil-provided zoneinfo. |
|
||||||
""" |
|
||||||
warnings.warn("zoneinfo.gettz() will be removed in future versions, " |
|
||||||
"to use the dateutil-provided zoneinfo files, instantiate a " |
|
||||||
"ZoneInfoFile object and use ZoneInfoFile.zones.get() " |
|
||||||
"instead. See the documentation for details.", |
|
||||||
DeprecationWarning) |
|
||||||
|
|
||||||
if len(_CLASS_ZONE_INSTANCE) == 0: |
|
||||||
_CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream())) |
|
||||||
return _CLASS_ZONE_INSTANCE[0].zones.get(name) |
|
||||||
|
|
||||||
|
|
||||||
def gettz_db_metadata(): |
|
||||||
""" Get the zonefile metadata |
|
||||||
|
|
||||||
See `zonefile_metadata`_ |
|
||||||
|
|
||||||
:returns: |
|
||||||
A dictionary with the database metadata |
|
||||||
|
|
||||||
.. deprecated:: 2.6 |
|
||||||
See deprecation warning in :func:`zoneinfo.gettz`. To get metadata, |
|
||||||
query the attribute ``zoneinfo.ZoneInfoFile.metadata``. |
|
||||||
""" |
|
||||||
warnings.warn("zoneinfo.gettz_db_metadata() will be removed in future " |
|
||||||
"versions, to use the dateutil-provided zoneinfo files, " |
|
||||||
"ZoneInfoFile object and query the 'metadata' attribute " |
|
||||||
"instead. See the documentation for details.", |
|
||||||
DeprecationWarning) |
|
||||||
|
|
||||||
if len(_CLASS_ZONE_INSTANCE) == 0: |
|
||||||
_CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream())) |
|
||||||
return _CLASS_ZONE_INSTANCE[0].metadata |
|
Binary file not shown.
@ -1,53 +0,0 @@ |
|||||||
import logging |
|
||||||
import os |
|
||||||
import tempfile |
|
||||||
import shutil |
|
||||||
import json |
|
||||||
from subprocess import check_call |
|
||||||
from tarfile import TarFile |
|
||||||
|
|
||||||
from dateutil.zoneinfo import METADATA_FN, ZONEFILENAME |
|
||||||
|
|
||||||
|
|
||||||
def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None): |
|
||||||
"""Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar* |
|
||||||
|
|
||||||
filename is the timezone tarball from ``ftp.iana.org/tz``. |
|
||||||
|
|
||||||
""" |
|
||||||
tmpdir = tempfile.mkdtemp() |
|
||||||
zonedir = os.path.join(tmpdir, "zoneinfo") |
|
||||||
moduledir = os.path.dirname(__file__) |
|
||||||
try: |
|
||||||
with TarFile.open(filename) as tf: |
|
||||||
for name in zonegroups: |
|
||||||
tf.extract(name, tmpdir) |
|
||||||
filepaths = [os.path.join(tmpdir, n) for n in zonegroups] |
|
||||||
try: |
|
||||||
check_call(["zic", "-d", zonedir] + filepaths) |
|
||||||
except OSError as e: |
|
||||||
_print_on_nosuchfile(e) |
|
||||||
raise |
|
||||||
# write metadata file |
|
||||||
with open(os.path.join(zonedir, METADATA_FN), 'w') as f: |
|
||||||
json.dump(metadata, f, indent=4, sort_keys=True) |
|
||||||
target = os.path.join(moduledir, ZONEFILENAME) |
|
||||||
with TarFile.open(target, "w:%s" % format) as tf: |
|
||||||
for entry in os.listdir(zonedir): |
|
||||||
entrypath = os.path.join(zonedir, entry) |
|
||||||
tf.add(entrypath, entry) |
|
||||||
finally: |
|
||||||
shutil.rmtree(tmpdir) |
|
||||||
|
|
||||||
|
|
||||||
def _print_on_nosuchfile(e): |
|
||||||
"""Print helpful troubleshooting message |
|
||||||
|
|
||||||
e is an exception raised by subprocess.check_call() |
|
||||||
|
|
||||||
""" |
|
||||||
if e.errno == 2: |
|
||||||
logging.error( |
|
||||||
"Could not find zic. Perhaps you need to install " |
|
||||||
"libc-bin or some other package that provides it, " |
|
||||||
"or it's not in your PATH?") |
|
@ -1 +0,0 @@ |
|||||||
pip |
|
@ -1,26 +0,0 @@ |
|||||||
Copyright (c) 2005-2018, Michele Simionato |
|
||||||
All rights reserved. |
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without |
|
||||||
modification, are permitted provided that the following conditions are |
|
||||||
met: |
|
||||||
|
|
||||||
Redistributions of source code must retain the above copyright |
|
||||||
notice, this list of conditions and the following disclaimer. |
|
||||||
Redistributions in bytecode form must reproduce the above copyright |
|
||||||
notice, this list of conditions and the following disclaimer in |
|
||||||
the documentation and/or other materials provided with the |
|
||||||
distribution. |
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
||||||
HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
|
||||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
|
||||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
|
||||||
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
|
||||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR |
|
||||||
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE |
|
||||||
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
|
||||||
DAMAGE. |
|
@ -1,131 +0,0 @@ |
|||||||
Metadata-Version: 2.1 |
|
||||||
Name: decorator |
|
||||||
Version: 4.4.2 |
|
||||||
Summary: Decorators for Humans |
|
||||||
Home-page: https://github.com/micheles/decorator |
|
||||||
Author: Michele Simionato |
|
||||||
Author-email: michele.simionato@gmail.com |
|
||||||
License: new BSD License |
|
||||||
Keywords: decorators generic utility |
|
||||||
Platform: All |
|
||||||
Classifier: Development Status :: 5 - Production/Stable |
|
||||||
Classifier: Intended Audience :: Developers |
|
||||||
Classifier: License :: OSI Approved :: BSD License |
|
||||||
Classifier: Natural Language :: English |
|
||||||
Classifier: Operating System :: OS Independent |
|
||||||
Classifier: Programming Language :: Python |
|
||||||
Classifier: Programming Language :: Python :: 2 |
|
||||||
Classifier: Programming Language :: Python :: 2.6 |
|
||||||
Classifier: Programming Language :: Python :: 2.7 |
|
||||||
Classifier: Programming Language :: Python :: 3 |
|
||||||
Classifier: Programming Language :: Python :: 3.2 |
|
||||||
Classifier: Programming Language :: Python :: 3.3 |
|
||||||
Classifier: Programming Language :: Python :: 3.4 |
|
||||||
Classifier: Programming Language :: Python :: 3.5 |
|
||||||
Classifier: Programming Language :: Python :: 3.6 |
|
||||||
Classifier: Programming Language :: Python :: 3.7 |
|
||||||
Classifier: Programming Language :: Python :: Implementation :: CPython |
|
||||||
Classifier: Topic :: Software Development :: Libraries |
|
||||||
Classifier: Topic :: Utilities |
|
||||||
Requires-Python: >=2.6, !=3.0.*, !=3.1.* |
|
||||||
|
|
||||||
Decorators for Humans |
|
||||||
===================== |
|
||||||
|
|
||||||
The goal of the decorator module is to make it easy to define |
|
||||||
signature-preserving function decorators and decorator factories. |
|
||||||
It also includes an implementation of multiple dispatch and other niceties |
|
||||||
(please check the docs). It is released under a two-clauses |
|
||||||
BSD license, i.e. basically you can do whatever you want with it but I am not |
|
||||||
responsible. |
|
||||||
|
|
||||||
Installation |
|
||||||
------------- |
|
||||||
|
|
||||||
If you are lazy, just perform |
|
||||||
|
|
||||||
``$ pip install decorator`` |
|
||||||
|
|
||||||
which will install just the module on your system. |
|
||||||
|
|
||||||
If you prefer to install the full distribution from source, including |
|
||||||
the documentation, clone the `GitHub repo`_ or download the tarball_, unpack it and run |
|
||||||
|
|
||||||
``$ pip install .`` |
|
||||||
|
|
||||||
in the main directory, possibly as superuser. |
|
||||||
|
|
||||||
.. _tarball: https://pypi.org/project/decorator/#files |
|
||||||
.. _GitHub repo: https://github.com/micheles/decorator |
|
||||||
|
|
||||||
Testing |
|
||||||
-------- |
|
||||||
|
|
||||||
If you have the source code installation you can run the tests with |
|
||||||
|
|
||||||
`$ python src/tests/test.py -v` |
|
||||||
|
|
||||||
or (if you have setuptools installed) |
|
||||||
|
|
||||||
`$ python setup.py test` |
|
||||||
|
|
||||||
Notice that you may run into trouble if in your system there |
|
||||||
is an older version of the decorator module; in such a case remove the |
|
||||||
old version. It is safe even to copy the module `decorator.py` over |
|
||||||
an existing one, since we kept backward-compatibility for a long time. |
|
||||||
|
|
||||||
Repository |
|
||||||
--------------- |
|
||||||
|
|
||||||
The project is hosted on GitHub. You can look at the source here: |
|
||||||
|
|
||||||
https://github.com/micheles/decorator |
|
||||||
|
|
||||||
Documentation |
|
||||||
--------------- |
|
||||||
|
|
||||||
The documentation has been moved to https://github.com/micheles/decorator/blob/master/docs/documentation.md |
|
||||||
|
|
||||||
From there you can get a PDF version by simply using the print |
|
||||||
functionality of your browser. |
|
||||||
|
|
||||||
Here is the documentation for previous versions of the module: |
|
||||||
|
|
||||||
https://github.com/micheles/decorator/blob/4.3.2/docs/tests.documentation.rst |
|
||||||
https://github.com/micheles/decorator/blob/4.2.1/docs/tests.documentation.rst |
|
||||||
https://github.com/micheles/decorator/blob/4.1.2/docs/tests.documentation.rst |
|
||||||
https://github.com/micheles/decorator/blob/4.0.0/documentation.rst |
|
||||||
https://github.com/micheles/decorator/blob/3.4.2/documentation.rst |
|
||||||
|
|
||||||
For the impatient |
|
||||||
----------------- |
|
||||||
|
|
||||||
Here is an example of how to define a family of decorators tracing slow |
|
||||||
operations: |
|
||||||
|
|
||||||
.. code-block:: python |
|
||||||
|
|
||||||
from decorator import decorator |
|
||||||
|
|
||||||
@decorator |
|
||||||
def warn_slow(func, timelimit=60, *args, **kw): |
|
||||||
t0 = time.time() |
|
||||||
result = func(*args, **kw) |
|
||||||
dt = time.time() - t0 |
|
||||||
if dt > timelimit: |
|
||||||
logging.warn('%s took %d seconds', func.__name__, dt) |
|
||||||
else: |
|
||||||
logging.info('%s took %d seconds', func.__name__, dt) |
|
||||||
return result |
|
||||||
|
|
||||||
@warn_slow # warn if it takes more than 1 minute |
|
||||||
def preprocess_input_files(inputdir, tempdir): |
|
||||||
... |
|
||||||
|
|
||||||
@warn_slow(timelimit=600) # warn if it takes more than 10 minutes |
|
||||||
def run_calculation(tempdir, outdir): |
|
||||||
... |
|
||||||
|
|
||||||
Enjoy! |
|
||||||
|
|
||||||
|
|
@ -1,9 +0,0 @@ |
|||||||
__pycache__/decorator.cpython-39.pyc,, |
|
||||||
decorator-4.4.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 |
|
||||||
decorator-4.4.2.dist-info/LICENSE.txt,sha256=_RFmDKvwUyCCxFcGhi-vwpSQfsf44heBgkCkmZgGeC4,1309 |
|
||||||
decorator-4.4.2.dist-info/METADATA,sha256=RYLh5Qy8XzYOcgCT6RsI_cTXG_PE1QvoAVT-u2vus80,4168 |
|
||||||
decorator-4.4.2.dist-info/RECORD,, |
|
||||||
decorator-4.4.2.dist-info/WHEEL,sha256=h_aVn5OB2IERUjMbi2pucmR_zzWJtk303YXvhh60NJ8,110 |
|
||||||
decorator-4.4.2.dist-info/pbr.json,sha256=AL84oUUWQHwkd8OCPhLRo2NJjU5MDdmXMqRHv-posqs,47 |
|
||||||
decorator-4.4.2.dist-info/top_level.txt,sha256=Kn6eQjo83ctWxXVyBMOYt0_YpjRjBznKYVuNyuC_DSI,10 |
|
||||||
decorator.py,sha256=aQ8Ozc-EK26xBTOXVR5A-8Szgx99_bhaexZSGNn38Yc,17222 |
|
@ -1,6 +0,0 @@ |
|||||||
Wheel-Version: 1.0 |
|
||||||
Generator: bdist_wheel (0.33.4) |
|
||||||
Root-Is-Purelib: true |
|
||||||
Tag: py2-none-any |
|
||||||
Tag: py3-none-any |
|
||||||
|
|
@ -1 +0,0 @@ |
|||||||
decorator |
|
@ -1,454 +0,0 @@ |
|||||||
# ######################### LICENSE ############################ # |
|
||||||
|
|
||||||
# Copyright (c) 2005-2018, Michele Simionato |
|
||||||
# All rights reserved. |
|
||||||
|
|
||||||
# Redistribution and use in source and binary forms, with or without |
|
||||||
# modification, are permitted provided that the following conditions are |
|
||||||
# met: |
|
||||||
|
|
||||||
# Redistributions of source code must retain the above copyright |
|
||||||
# notice, this list of conditions and the following disclaimer. |
|
||||||
# Redistributions in bytecode form must reproduce the above copyright |
|
||||||
# notice, this list of conditions and the following disclaimer in |
|
||||||
# the documentation and/or other materials provided with the |
|
||||||
# distribution. |
|
||||||
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
||||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
||||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
||||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
||||||
# HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
|
||||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
|
||||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
|
||||||
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
|
||||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR |
|
||||||
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE |
|
||||||
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
|
||||||
# DAMAGE. |
|
||||||
|
|
||||||
""" |
|
||||||
Decorator module, see http://pypi.python.org/pypi/decorator |
|
||||||
for the documentation. |
|
||||||
""" |
|
||||||
from __future__ import print_function |
|
||||||
|
|
||||||
import re |
|
||||||
import sys |
|
||||||
import inspect |
|
||||||
import operator |
|
||||||
import itertools |
|
||||||
import collections |
|
||||||
|
|
||||||
__version__ = '4.4.2' |
|
||||||
|
|
||||||
if sys.version_info >= (3,): |
|
||||||
from inspect import getfullargspec |
|
||||||
|
|
||||||
def get_init(cls): |
|
||||||
return cls.__init__ |
|
||||||
else: |
|
||||||
FullArgSpec = collections.namedtuple( |
|
||||||
'FullArgSpec', 'args varargs varkw defaults ' |
|
||||||
'kwonlyargs kwonlydefaults annotations') |
|
||||||
|
|
||||||
def getfullargspec(f): |
|
||||||
"A quick and dirty replacement for getfullargspec for Python 2.X" |
|
||||||
return FullArgSpec._make(inspect.getargspec(f) + ([], None, {})) |
|
||||||
|
|
||||||
def get_init(cls): |
|
||||||
return cls.__init__.__func__ |
|
||||||
|
|
||||||
try: |
|
||||||
iscoroutinefunction = inspect.iscoroutinefunction |
|
||||||
except AttributeError: |
|
||||||
# let's assume there are no coroutine functions in old Python |
|
||||||
def iscoroutinefunction(f): |
|
||||||
return False |
|
||||||
try: |
|
||||||
from inspect import isgeneratorfunction |
|
||||||
except ImportError: |
|
||||||
# assume no generator function in old Python versions |
|
||||||
def isgeneratorfunction(caller): |
|
||||||
return False |
|
||||||
|
|
||||||
|
|
||||||
DEF = re.compile(r'\s*def\s*([_\w][_\w\d]*)\s*\(') |
|
||||||
|
|
||||||
|
|
||||||
# basic functionality |
|
||||||
class FunctionMaker(object): |
|
||||||
""" |
|
||||||
An object with the ability to create functions with a given signature. |
|
||||||
It has attributes name, doc, module, signature, defaults, dict and |
|
||||||
methods update and make. |
|
||||||
""" |
|
||||||
|
|
||||||
# Atomic get-and-increment provided by the GIL |
|
||||||
_compile_count = itertools.count() |
|
||||||
|
|
||||||
# make pylint happy |
|
||||||
args = varargs = varkw = defaults = kwonlyargs = kwonlydefaults = () |
|
||||||
|
|
||||||
def __init__(self, func=None, name=None, signature=None, |
|
||||||
defaults=None, doc=None, module=None, funcdict=None): |
|
||||||
self.shortsignature = signature |
|
||||||
if func: |
|
||||||
# func can be a class or a callable, but not an instance method |
|
||||||
self.name = func.__name__ |
|
||||||
if self.name == '<lambda>': # small hack for lambda functions |
|
||||||
self.name = '_lambda_' |
|
||||||
self.doc = func.__doc__ |
|
||||||
self.module = func.__module__ |
|
||||||
if inspect.isfunction(func): |
|
||||||
argspec = getfullargspec(func) |
|
||||||
self.annotations = getattr(func, '__annotations__', {}) |
|
||||||
for a in ('args', 'varargs', 'varkw', 'defaults', 'kwonlyargs', |
|
||||||
'kwonlydefaults'): |
|
||||||
setattr(self, a, getattr(argspec, a)) |
|
||||||
for i, arg in enumerate(self.args): |
|
||||||
setattr(self, 'arg%d' % i, arg) |
|
||||||
allargs = list(self.args) |
|
||||||
allshortargs = list(self.args) |
|
||||||
if self.varargs: |
|
||||||
allargs.append('*' + self.varargs) |
|
||||||
allshortargs.append('*' + self.varargs) |
|
||||||
elif self.kwonlyargs: |
|
||||||
allargs.append('*') # single star syntax |
|
||||||
for a in self.kwonlyargs: |
|
||||||
allargs.append('%s=None' % a) |
|
||||||
allshortargs.append('%s=%s' % (a, a)) |
|
||||||
if self.varkw: |
|
||||||
allargs.append('**' + self.varkw) |
|
||||||
allshortargs.append('**' + self.varkw) |
|
||||||
self.signature = ', '.join(allargs) |
|
||||||
self.shortsignature = ', '.join(allshortargs) |
|
||||||
self.dict = func.__dict__.copy() |
|
||||||
# func=None happens when decorating a caller |
|
||||||
if name: |
|
||||||
self.name = name |
|
||||||
if signature is not None: |
|
||||||
self.signature = signature |
|
||||||
if defaults: |
|
||||||
self.defaults = defaults |
|
||||||
if doc: |
|
||||||
self.doc = doc |
|
||||||
if module: |
|
||||||
self.module = module |
|
||||||
if funcdict: |
|
||||||
self.dict = funcdict |
|
||||||
# check existence required attributes |
|
||||||
assert hasattr(self, 'name') |
|
||||||
if not hasattr(self, 'signature'): |
|
||||||
raise TypeError('You are decorating a non function: %s' % func) |
|
||||||
|
|
||||||
def update(self, func, **kw): |
|
||||||
"Update the signature of func with the data in self" |
|
||||||
func.__name__ = self.name |
|
||||||
func.__doc__ = getattr(self, 'doc', None) |
|
||||||
func.__dict__ = getattr(self, 'dict', {}) |
|
||||||
func.__defaults__ = self.defaults |
|
||||||
func.__kwdefaults__ = self.kwonlydefaults or None |
|
||||||
func.__annotations__ = getattr(self, 'annotations', None) |
|
||||||
try: |
|
||||||
frame = sys._getframe(3) |
|
||||||
except AttributeError: # for IronPython and similar implementations |
|
||||||
callermodule = '?' |
|
||||||
else: |
|
||||||
callermodule = frame.f_globals.get('__name__', '?') |
|
||||||
func.__module__ = getattr(self, 'module', callermodule) |
|
||||||
func.__dict__.update(kw) |
|
||||||
|
|
||||||
def make(self, src_templ, evaldict=None, addsource=False, **attrs): |
|
||||||
"Make a new function from a given template and update the signature" |
|
||||||
src = src_templ % vars(self) # expand name and signature |
|
||||||
evaldict = evaldict or {} |
|
||||||
mo = DEF.search(src) |
|
||||||
if mo is None: |
|
||||||
raise SyntaxError('not a valid function template\n%s' % src) |
|
||||||
name = mo.group(1) # extract the function name |
|
||||||
names = set([name] + [arg.strip(' *') for arg in |
|
||||||
self.shortsignature.split(',')]) |
|
||||||
for n in names: |
|
||||||
if n in ('_func_', '_call_'): |
|
||||||
raise NameError('%s is overridden in\n%s' % (n, src)) |
|
||||||
|
|
||||||
if not src.endswith('\n'): # add a newline for old Pythons |
|
||||||
src += '\n' |
|
||||||
|
|
||||||
# Ensure each generated function has a unique filename for profilers |
|
||||||
# (such as cProfile) that depend on the tuple of (<filename>, |
|
||||||
# <definition line>, <function name>) being unique. |
|
||||||
filename = '<decorator-gen-%d>' % next(self._compile_count) |
|
||||||
try: |
|
||||||
code = compile(src, filename, 'single') |
|
||||||
exec(code, evaldict) |
|
||||||
except Exception: |
|
||||||
print('Error in generated code:', file=sys.stderr) |
|
||||||
print(src, file=sys.stderr) |
|
||||||
raise |
|
||||||
func = evaldict[name] |
|
||||||
if addsource: |
|
||||||
attrs['__source__'] = src |
|
||||||
self.update(func, **attrs) |
|
||||||
return func |
|
||||||
|
|
||||||
@classmethod |
|
||||||
def create(cls, obj, body, evaldict, defaults=None, |
|
||||||
doc=None, module=None, addsource=True, **attrs): |
|
||||||
""" |
|
||||||
Create a function from the strings name, signature and body. |
|
||||||
evaldict is the evaluation dictionary. If addsource is true an |
|
||||||
attribute __source__ is added to the result. The attributes attrs |
|
||||||
are added, if any. |
|
||||||
""" |
|
||||||
if isinstance(obj, str): # "name(signature)" |
|
||||||
name, rest = obj.strip().split('(', 1) |
|
||||||
signature = rest[:-1] # strip a right parens |
|
||||||
func = None |
|
||||||
else: # a function |
|
||||||
name = None |
|
||||||
signature = None |
|
||||||
func = obj |
|
||||||
self = cls(func, name, signature, defaults, doc, module) |
|
||||||
ibody = '\n'.join(' ' + line for line in body.splitlines()) |
|
||||||
caller = evaldict.get('_call_') # when called from `decorate` |
|
||||||
if caller and iscoroutinefunction(caller): |
|
||||||
body = ('async def %(name)s(%(signature)s):\n' + ibody).replace( |
|
||||||
'return', 'return await') |
|
||||||
else: |
|
||||||
body = 'def %(name)s(%(signature)s):\n' + ibody |
|
||||||
return self.make(body, evaldict, addsource, **attrs) |
|
||||||
|
|
||||||
|
|
||||||
def decorate(func, caller, extras=()): |
|
||||||
""" |
|
||||||
decorate(func, caller) decorates a function using a caller. |
|
||||||
If the caller is a generator function, the resulting function |
|
||||||
will be a generator function. |
|
||||||
""" |
|
||||||
evaldict = dict(_call_=caller, _func_=func) |
|
||||||
es = '' |
|
||||||
for i, extra in enumerate(extras): |
|
||||||
ex = '_e%d_' % i |
|
||||||
evaldict[ex] = extra |
|
||||||
es += ex + ', ' |
|
||||||
|
|
||||||
if '3.5' <= sys.version < '3.6': |
|
||||||
# with Python 3.5 isgeneratorfunction returns True for all coroutines |
|
||||||
# however we know that it is NOT possible to have a generator |
|
||||||
# coroutine in python 3.5: PEP525 was not there yet |
|
||||||
generatorcaller = isgeneratorfunction( |
|
||||||
caller) and not iscoroutinefunction(caller) |
|
||||||
else: |
|
||||||
generatorcaller = isgeneratorfunction(caller) |
|
||||||
if generatorcaller: |
|
||||||
fun = FunctionMaker.create( |
|
||||||
func, "for res in _call_(_func_, %s%%(shortsignature)s):\n" |
|
||||||
" yield res" % es, evaldict, __wrapped__=func) |
|
||||||
else: |
|
||||||
fun = FunctionMaker.create( |
|
||||||
func, "return _call_(_func_, %s%%(shortsignature)s)" % es, |
|
||||||
evaldict, __wrapped__=func) |
|
||||||
if hasattr(func, '__qualname__'): |
|
||||||
fun.__qualname__ = func.__qualname__ |
|
||||||
return fun |
|
||||||
|
|
||||||
|
|
||||||
def decorator(caller, _func=None): |
|
||||||
"""decorator(caller) converts a caller function into a decorator""" |
|
||||||
if _func is not None: # return a decorated function |
|
||||||
# this is obsolete behavior; you should use decorate instead |
|
||||||
return decorate(_func, caller) |
|
||||||
# else return a decorator function |
|
||||||
defaultargs, defaults = '', () |
|
||||||
if inspect.isclass(caller): |
|
||||||
name = caller.__name__.lower() |
|
||||||
doc = 'decorator(%s) converts functions/generators into ' \ |
|
||||||
'factories of %s objects' % (caller.__name__, caller.__name__) |
|
||||||
elif inspect.isfunction(caller): |
|
||||||
if caller.__name__ == '<lambda>': |
|
||||||
name = '_lambda_' |
|
||||||
else: |
|
||||||
name = caller.__name__ |
|
||||||
doc = caller.__doc__ |
|
||||||
nargs = caller.__code__.co_argcount |
|
||||||
ndefs = len(caller.__defaults__ or ()) |
|
||||||
defaultargs = ', '.join(caller.__code__.co_varnames[nargs-ndefs:nargs]) |
|
||||||
if defaultargs: |
|
||||||
defaultargs += ',' |
|
||||||
defaults = caller.__defaults__ |
|
||||||
else: # assume caller is an object with a __call__ method |
|
||||||
name = caller.__class__.__name__.lower() |
|
||||||
doc = caller.__call__.__doc__ |
|
||||||
evaldict = dict(_call=caller, _decorate_=decorate) |
|
||||||
dec = FunctionMaker.create( |
|
||||||
'%s(func, %s)' % (name, defaultargs), |
|
||||||
'if func is None: return lambda func: _decorate_(func, _call, (%s))\n' |
|
||||||
'return _decorate_(func, _call, (%s))' % (defaultargs, defaultargs), |
|
||||||
evaldict, doc=doc, module=caller.__module__, __wrapped__=caller) |
|
||||||
if defaults: |
|
||||||
dec.__defaults__ = (None,) + defaults |
|
||||||
return dec |
|
||||||
|
|
||||||
|
|
||||||
# ####################### contextmanager ####################### # |
|
||||||
|
|
||||||
try: # Python >= 3.2 |
|
||||||
from contextlib import _GeneratorContextManager |
|
||||||
except ImportError: # Python >= 2.5 |
|
||||||
from contextlib import GeneratorContextManager as _GeneratorContextManager |
|
||||||
|
|
||||||
|
|
||||||
class ContextManager(_GeneratorContextManager): |
|
||||||
def __call__(self, func): |
|
||||||
"""Context manager decorator""" |
|
||||||
return FunctionMaker.create( |
|
||||||
func, "with _self_: return _func_(%(shortsignature)s)", |
|
||||||
dict(_self_=self, _func_=func), __wrapped__=func) |
|
||||||
|
|
||||||
|
|
||||||
init = getfullargspec(_GeneratorContextManager.__init__) |
|
||||||
n_args = len(init.args) |
|
||||||
if n_args == 2 and not init.varargs: # (self, genobj) Python 2.7 |
|
||||||
def __init__(self, g, *a, **k): |
|
||||||
return _GeneratorContextManager.__init__(self, g(*a, **k)) |
|
||||||
ContextManager.__init__ = __init__ |
|
||||||
elif n_args == 2 and init.varargs: # (self, gen, *a, **k) Python 3.4 |
|
||||||
pass |
|
||||||
elif n_args == 4: # (self, gen, args, kwds) Python 3.5 |
|
||||||
def __init__(self, g, *a, **k): |
|
||||||
return _GeneratorContextManager.__init__(self, g, a, k) |
|
||||||
ContextManager.__init__ = __init__ |
|
||||||
|
|
||||||
_contextmanager = decorator(ContextManager) |
|
||||||
|
|
||||||
|
|
||||||
def contextmanager(func): |
|
||||||
# Enable Pylint config: contextmanager-decorators=decorator.contextmanager |
|
||||||
return _contextmanager(func) |
|
||||||
|
|
||||||
|
|
||||||
# ############################ dispatch_on ############################ # |
|
||||||
|
|
||||||
def append(a, vancestors): |
|
||||||
""" |
|
||||||
Append ``a`` to the list of the virtual ancestors, unless it is already |
|
||||||
included. |
|
||||||
""" |
|
||||||
add = True |
|
||||||
for j, va in enumerate(vancestors): |
|
||||||
if issubclass(va, a): |
|
||||||
add = False |
|
||||||
break |
|
||||||
if issubclass(a, va): |
|
||||||
vancestors[j] = a |
|
||||||
add = False |
|
||||||
if add: |
|
||||||
vancestors.append(a) |
|
||||||
|
|
||||||
|
|
||||||
# inspired from simplegeneric by P.J. Eby and functools.singledispatch |
|
||||||
def dispatch_on(*dispatch_args): |
|
||||||
""" |
|
||||||
Factory of decorators turning a function into a generic function |
|
||||||
dispatching on the given arguments. |
|
||||||
""" |
|
||||||
assert dispatch_args, 'No dispatch args passed' |
|
||||||
dispatch_str = '(%s,)' % ', '.join(dispatch_args) |
|
||||||
|
|
||||||
def check(arguments, wrong=operator.ne, msg=''): |
|
||||||
"""Make sure one passes the expected number of arguments""" |
|
||||||
if wrong(len(arguments), len(dispatch_args)): |
|
||||||
raise TypeError('Expected %d arguments, got %d%s' % |
|
||||||
(len(dispatch_args), len(arguments), msg)) |
|
||||||
|
|
||||||
def gen_func_dec(func): |
|
||||||
"""Decorator turning a function into a generic function""" |
|
||||||
|
|
||||||
# first check the dispatch arguments |
|
||||||
argset = set(getfullargspec(func).args) |
|
||||||
if not set(dispatch_args) <= argset: |
|
||||||
raise NameError('Unknown dispatch arguments %s' % dispatch_str) |
|
||||||
|
|
||||||
typemap = {} |
|
||||||
|
|
||||||
def vancestors(*types): |
|
||||||
""" |
|
||||||
Get a list of sets of virtual ancestors for the given types |
|
||||||
""" |
|
||||||
check(types) |
|
||||||
ras = [[] for _ in range(len(dispatch_args))] |
|
||||||
for types_ in typemap: |
|
||||||
for t, type_, ra in zip(types, types_, ras): |
|
||||||
if issubclass(t, type_) and type_ not in t.mro(): |
|
||||||
append(type_, ra) |
|
||||||
return [set(ra) for ra in ras] |
|
||||||
|
|
||||||
def ancestors(*types): |
|
||||||
""" |
|
||||||
Get a list of virtual MROs, one for each type |
|
||||||
""" |
|
||||||
check(types) |
|
||||||
lists = [] |
|
||||||
for t, vas in zip(types, vancestors(*types)): |
|
||||||
n_vas = len(vas) |
|
||||||
if n_vas > 1: |
|
||||||
raise RuntimeError( |
|
||||||
'Ambiguous dispatch for %s: %s' % (t, vas)) |
|
||||||
elif n_vas == 1: |
|
||||||
va, = vas |
|
||||||
mro = type('t', (t, va), {}).mro()[1:] |
|
||||||
else: |
|
||||||
mro = t.mro() |
|
||||||
lists.append(mro[:-1]) # discard t and object |
|
||||||
return lists |
|
||||||
|
|
||||||
def register(*types): |
|
||||||
""" |
|
||||||
Decorator to register an implementation for the given types |
|
||||||
""" |
|
||||||
check(types) |
|
||||||
|
|
||||||
def dec(f): |
|
||||||
check(getfullargspec(f).args, operator.lt, ' in ' + f.__name__) |
|
||||||
typemap[types] = f |
|
||||||
return f |
|
||||||
return dec |
|
||||||
|
|
||||||
def dispatch_info(*types): |
|
||||||
""" |
|
||||||
An utility to introspect the dispatch algorithm |
|
||||||
""" |
|
||||||
check(types) |
|
||||||
lst = [] |
|
||||||
for anc in itertools.product(*ancestors(*types)): |
|
||||||
lst.append(tuple(a.__name__ for a in anc)) |
|
||||||
return lst |
|
||||||
|
|
||||||
def _dispatch(dispatch_args, *args, **kw): |
|
||||||
types = tuple(type(arg) for arg in dispatch_args) |
|
||||||
try: # fast path |
|
||||||
f = typemap[types] |
|
||||||
except KeyError: |
|
||||||
pass |
|
||||||
else: |
|
||||||
return f(*args, **kw) |
|
||||||
combinations = itertools.product(*ancestors(*types)) |
|
||||||
next(combinations) # the first one has been already tried |
|
||||||
for types_ in combinations: |
|
||||||
f = typemap.get(types_) |
|
||||||
if f is not None: |
|
||||||
return f(*args, **kw) |
|
||||||
|
|
||||||
# else call the default implementation |
|
||||||
return func(*args, **kw) |
|
||||||
|
|
||||||
return FunctionMaker.create( |
|
||||||
func, 'return _f_(%s, %%(shortsignature)s)' % dispatch_str, |
|
||||||
dict(_f_=_dispatch), register=register, default=func, |
|
||||||
typemap=typemap, vancestors=vancestors, ancestors=ancestors, |
|
||||||
dispatch_info=dispatch_info, __wrapped__=func) |
|
||||||
|
|
||||||
gen_func_dec.__name__ = 'dispatch_on' + dispatch_str |
|
||||||
return gen_func_dec |
|
@ -1 +0,0 @@ |
|||||||
import os; var = 'SETUPTOOLS_USE_DISTUTILS'; enabled = os.environ.get(var, 'stdlib') == 'local'; enabled and __import__('_distutils_hack').add_shim(); |
|
@ -1,5 +0,0 @@ |
|||||||
"""Run the EasyInstall command""" |
|
||||||
|
|
||||||
if __name__ == '__main__': |
|
||||||
from setuptools.command.easy_install import main |
|
||||||
main() |
|
@ -1 +0,0 @@ |
|||||||
pip |
|
@ -1,40 +0,0 @@ |
|||||||
License |
|
||||||
======= |
|
||||||
|
|
||||||
NetworkX is distributed with the 3-clause BSD license. |
|
||||||
|
|
||||||
:: |
|
||||||
|
|
||||||
Copyright (C) 2004-2020, NetworkX Developers |
|
||||||
Aric Hagberg <hagberg@lanl.gov> |
|
||||||
Dan Schult <dschult@colgate.edu> |
|
||||||
Pieter Swart <swart@lanl.gov> |
|
||||||
All rights reserved. |
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without |
|
||||||
modification, are permitted provided that the following conditions are |
|
||||||
met: |
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright |
|
||||||
notice, this list of conditions and the following disclaimer. |
|
||||||
|
|
||||||
* Redistributions in binary form must reproduce the above |
|
||||||
copyright notice, this list of conditions and the following |
|
||||||
disclaimer in the documentation and/or other materials provided |
|
||||||
with the distribution. |
|
||||||
|
|
||||||
* Neither the name of the NetworkX Developers nor the names of its |
|
||||||
contributors may be used to endorse or promote products derived |
|
||||||
from this software without specific prior written permission. |
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
@ -1,142 +0,0 @@ |
|||||||
Metadata-Version: 2.1 |
|
||||||
Name: networkx |
|
||||||
Version: 2.5 |
|
||||||
Summary: Python package for creating and manipulating graphs and networks |
|
||||||
Home-page: http://networkx.github.io/ |
|
||||||
Author: Aric Hagberg |
|
||||||
Author-email: hagberg@lanl.gov |
|
||||||
Maintainer: NetworkX Developers |
|
||||||
Maintainer-email: networkx-discuss@googlegroups.com |
|
||||||
License: UNKNOWN |
|
||||||
Project-URL: Bug Tracker, https://github.com/networkx/networkx/issues |
|
||||||
Project-URL: Documentation, https://networkx.github.io/documentation/stable/ |
|
||||||
Project-URL: Source Code, https://github.com/networkx/networkx |
|
||||||
Keywords: Networks,Graph Theory,Mathematics,network,graph,discrete mathematics,math |
|
||||||
Platform: Linux |
|
||||||
Platform: Mac OSX |
|
||||||
Platform: Windows |
|
||||||
Platform: Unix |
|
||||||
Classifier: Development Status :: 5 - Production/Stable |
|
||||||
Classifier: Intended Audience :: Developers |
|
||||||
Classifier: Intended Audience :: Science/Research |
|
||||||
Classifier: License :: OSI Approved :: BSD License |
|
||||||
Classifier: Operating System :: OS Independent |
|
||||||
Classifier: Programming Language :: Python :: 3 |
|
||||||
Classifier: Programming Language :: Python :: 3.6 |
|
||||||
Classifier: Programming Language :: Python :: 3.7 |
|
||||||
Classifier: Programming Language :: Python :: 3.8 |
|
||||||
Classifier: Programming Language :: Python :: 3 :: Only |
|
||||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules |
|
||||||
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics |
|
||||||
Classifier: Topic :: Scientific/Engineering :: Information Analysis |
|
||||||
Classifier: Topic :: Scientific/Engineering :: Mathematics |
|
||||||
Classifier: Topic :: Scientific/Engineering :: Physics |
|
||||||
Requires-Python: >=3.6 |
|
||||||
Requires-Dist: decorator (>=4.3.0) |
|
||||||
Provides-Extra: all |
|
||||||
Requires-Dist: numpy ; extra == 'all' |
|
||||||
Requires-Dist: scipy ; extra == 'all' |
|
||||||
Requires-Dist: pandas ; extra == 'all' |
|
||||||
Requires-Dist: matplotlib ; extra == 'all' |
|
||||||
Requires-Dist: pygraphviz ; extra == 'all' |
|
||||||
Requires-Dist: pydot ; extra == 'all' |
|
||||||
Requires-Dist: pyyaml ; extra == 'all' |
|
||||||
Requires-Dist: lxml ; extra == 'all' |
|
||||||
Requires-Dist: pytest ; extra == 'all' |
|
||||||
Provides-Extra: gdal |
|
||||||
Requires-Dist: gdal ; extra == 'gdal' |
|
||||||
Provides-Extra: lxml |
|
||||||
Requires-Dist: lxml ; extra == 'lxml' |
|
||||||
Provides-Extra: matplotlib |
|
||||||
Requires-Dist: matplotlib ; extra == 'matplotlib' |
|
||||||
Provides-Extra: numpy |
|
||||||
Requires-Dist: numpy ; extra == 'numpy' |
|
||||||
Provides-Extra: pandas |
|
||||||
Requires-Dist: pandas ; extra == 'pandas' |
|
||||||
Provides-Extra: pydot |
|
||||||
Requires-Dist: pydot ; extra == 'pydot' |
|
||||||
Provides-Extra: pygraphviz |
|
||||||
Requires-Dist: pygraphviz ; extra == 'pygraphviz' |
|
||||||
Provides-Extra: pytest |
|
||||||
Requires-Dist: pytest ; extra == 'pytest' |
|
||||||
Provides-Extra: pyyaml |
|
||||||
Requires-Dist: pyyaml ; extra == 'pyyaml' |
|
||||||
Provides-Extra: scipy |
|
||||||
Requires-Dist: scipy ; extra == 'scipy' |
|
||||||
|
|
||||||
NetworkX |
|
||||||
======== |
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/v/networkx.svg |
|
||||||
:target: https://pypi.org/project/networkx/ |
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/pyversions/networkx.svg |
|
||||||
:target: https://pypi.org/project/networkx/ |
|
||||||
|
|
||||||
.. image:: https://travis-ci.org/networkx/networkx.svg?branch=master |
|
||||||
:target: https://travis-ci.org/networkx/networkx |
|
||||||
|
|
||||||
.. image:: https://ci.appveyor.com/api/projects/status/github/networkx/networkx?branch=master&svg=true |
|
||||||
:target: https://ci.appveyor.com/project/dschult/networkx-pqott |
|
||||||
|
|
||||||
.. image:: https://codecov.io/gh/networkx/networkx/branch/master/graph/badge.svg |
|
||||||
:target: https://codecov.io/gh/networkx/networkx |
|
||||||
|
|
||||||
NetworkX is a Python package for the creation, manipulation, |
|
||||||
and study of the structure, dynamics, and functions |
|
||||||
of complex networks. |
|
||||||
|
|
||||||
- **Website (including documentation):** https://networkx.github.io |
|
||||||
- **Mailing list:** https://groups.google.com/forum/#!forum/networkx-discuss |
|
||||||
- **Source:** https://github.com/networkx/networkx |
|
||||||
- **Bug reports:** https://github.com/networkx/networkx/issues |
|
||||||
|
|
||||||
Simple example |
|
||||||
-------------- |
|
||||||
|
|
||||||
Find the shortest path between two nodes in an undirected graph: |
|
||||||
|
|
||||||
.. code:: python |
|
||||||
|
|
||||||
>>> import networkx as nx |
|
||||||
>>> G = nx.Graph() |
|
||||||
>>> G.add_edge('A', 'B', weight=4) |
|
||||||
>>> G.add_edge('B', 'D', weight=2) |
|
||||||
>>> G.add_edge('A', 'C', weight=3) |
|
||||||
>>> G.add_edge('C', 'D', weight=4) |
|
||||||
>>> nx.shortest_path(G, 'A', 'D', weight='weight') |
|
||||||
['A', 'B', 'D'] |
|
||||||
|
|
||||||
Install |
|
||||||
------- |
|
||||||
|
|
||||||
Install the latest version of NetworkX:: |
|
||||||
|
|
||||||
$ pip install networkx |
|
||||||
|
|
||||||
Install with all optional dependencies:: |
|
||||||
|
|
||||||
$ pip install networkx[all] |
|
||||||
|
|
||||||
For additional details, please see `INSTALL.rst`. |
|
||||||
|
|
||||||
Bugs |
|
||||||
---- |
|
||||||
|
|
||||||
Please report any bugs that you find `here <https://github.com/networkx/networkx/issues>`_. |
|
||||||
Or, even better, fork the repository on `GitHub <https://github.com/networkx/networkx>`_ |
|
||||||
and create a pull request (PR). We welcome all changes, big or small, and we |
|
||||||
will help you make the PR if you are new to `git` (just ask on the issue and/or |
|
||||||
see `CONTRIBUTING.rst`). |
|
||||||
|
|
||||||
License |
|
||||||
------- |
|
||||||
|
|
||||||
Released under the 3-Clause BSD license (see `LICENSE.txt`):: |
|
||||||
|
|
||||||
Copyright (C) 2004-2020 NetworkX Developers |
|
||||||
Aric Hagberg <hagberg@lanl.gov> |
|
||||||
Dan Schult <dschult@colgate.edu> |
|
||||||
Pieter Swart <swart@lanl.gov> |
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +0,0 @@ |
|||||||
Wheel-Version: 1.0 |
|
||||||
Generator: bdist_wheel (0.35.1) |
|
||||||
Root-Is-Purelib: true |
|
||||||
Tag: py3-none-any |
|
||||||
|
|
@ -1 +0,0 @@ |
|||||||
networkx |
|
@ -1,76 +0,0 @@ |
|||||||
""" |
|
||||||
NetworkX |
|
||||||
======== |
|
||||||
|
|
||||||
NetworkX is a Python package for the creation, manipulation, and study of the |
|
||||||
structure, dynamics, and functions of complex networks. |
|
||||||
|
|
||||||
See https://networkx.github.io for complete documentation. |
|
||||||
""" |
|
||||||
|
|
||||||
import sys |
|
||||||
|
|
||||||
if sys.version_info[:2] < (3, 6): |
|
||||||
m = "Python 3.6 or later is required for NetworkX (%d.%d detected)." |
|
||||||
raise ImportError(m % sys.version_info[:2]) |
|
||||||
del sys |
|
||||||
|
|
||||||
# Release data |
|
||||||
from networkx import release |
|
||||||
|
|
||||||
|
|
||||||
__author__ = ( |
|
||||||
f"{release.authors['Hagberg'][0]} <{release.authors['Hagberg'][1]}>\n" |
|
||||||
f"{release.authors['Schult'][0]} <{release.authors['Schult'][1]}>\n" |
|
||||||
f"{release.authors['Swart'][0]} <{release.authors['Swart'][1]}>" |
|
||||||
) |
|
||||||
|
|
||||||
__date__ = release.date |
|
||||||
__version__ = release.version |
|
||||||
|
|
||||||
__bibtex__ = """@inproceedings{hagberg-2008-exploring, |
|
||||||
author = {Aric A. Hagberg and Daniel A. Schult and Pieter J. Swart}, |
|
||||||
title = {Exploring network structure, dynamics, and function using {NetworkX}}, |
|
||||||
year = {2008}, |
|
||||||
month = Aug, |
|
||||||
urlpdf = {http://math.lanl.gov/~hagberg/Papers/hagberg-2008-exploring.pdf}, |
|
||||||
booktitle = {Proceedings of the 7th Python in Science Conference (SciPy2008)}, |
|
||||||
editors = {G\"{a}el Varoquaux, Travis Vaught, and Jarrod Millman}, |
|
||||||
address = {Pasadena, CA USA}, |
|
||||||
pages = {11--15} |
|
||||||
}""" |
|
||||||
|
|
||||||
# These are import orderwise |
|
||||||
from networkx.exception import * |
|
||||||
import networkx.utils |
|
||||||
|
|
||||||
import networkx.classes.filters |
|
||||||
import networkx.classes |
|
||||||
from networkx.classes import * |
|
||||||
|
|
||||||
import networkx.convert |
|
||||||
from networkx.convert import * |
|
||||||
|
|
||||||
import networkx.convert_matrix |
|
||||||
from networkx.convert_matrix import * |
|
||||||
|
|
||||||
|
|
||||||
import networkx.relabel |
|
||||||
from networkx.relabel import * |
|
||||||
|
|
||||||
import networkx.generators |
|
||||||
from networkx.generators import * |
|
||||||
|
|
||||||
import networkx.readwrite |
|
||||||
from networkx.readwrite import * |
|
||||||
|
|
||||||
# Need to test with SciPy, when available |
|
||||||
import networkx.algorithms |
|
||||||
from networkx.algorithms import * |
|
||||||
import networkx.linalg |
|
||||||
|
|
||||||
from networkx.linalg import * |
|
||||||
from networkx.testing.test import run as test |
|
||||||
|
|
||||||
import networkx.drawing |
|
||||||
from networkx.drawing import * |
|
@ -1,125 +0,0 @@ |
|||||||
from networkx.algorithms.assortativity import * |
|
||||||
from networkx.algorithms.asteroidal import * |
|
||||||
from networkx.algorithms.boundary import * |
|
||||||
from networkx.algorithms.bridges import * |
|
||||||
from networkx.algorithms.chains import * |
|
||||||
from networkx.algorithms.centrality import * |
|
||||||
from networkx.algorithms.chordal import * |
|
||||||
from networkx.algorithms.cluster import * |
|
||||||
from networkx.algorithms.clique import * |
|
||||||
from networkx.algorithms.communicability_alg import * |
|
||||||
from networkx.algorithms.components import * |
|
||||||
from networkx.algorithms.coloring import * |
|
||||||
from networkx.algorithms.core import * |
|
||||||
from networkx.algorithms.covering import * |
|
||||||
from networkx.algorithms.cycles import * |
|
||||||
from networkx.algorithms.cuts import * |
|
||||||
from networkx.algorithms.d_separation import * |
|
||||||
from networkx.algorithms.dag import * |
|
||||||
from networkx.algorithms.distance_measures import * |
|
||||||
from networkx.algorithms.distance_regular import * |
|
||||||
from networkx.algorithms.dominance import * |
|
||||||
from networkx.algorithms.dominating import * |
|
||||||
from networkx.algorithms.efficiency_measures import * |
|
||||||
from networkx.algorithms.euler import * |
|
||||||
from networkx.algorithms.graphical import * |
|
||||||
from networkx.algorithms.hierarchy import * |
|
||||||
from networkx.algorithms.hybrid import * |
|
||||||
from networkx.algorithms.link_analysis import * |
|
||||||
from networkx.algorithms.link_prediction import * |
|
||||||
from networkx.algorithms.lowest_common_ancestors import * |
|
||||||
from networkx.algorithms.isolate import * |
|
||||||
from networkx.algorithms.matching import * |
|
||||||
from networkx.algorithms.minors import * |
|
||||||
from networkx.algorithms.mis import * |
|
||||||
from networkx.algorithms.moral import * |
|
||||||
from networkx.algorithms.non_randomness import * |
|
||||||
from networkx.algorithms.operators import * |
|
||||||
from networkx.algorithms.planarity import * |
|
||||||
from networkx.algorithms.planar_drawing import * |
|
||||||
from networkx.algorithms.reciprocity import * |
|
||||||
from networkx.algorithms.regular import * |
|
||||||
from networkx.algorithms.richclub import * |
|
||||||
from networkx.algorithms.shortest_paths import * |
|
||||||
from networkx.algorithms.similarity import * |
|
||||||
from networkx.algorithms.graph_hashing import * |
|
||||||
from networkx.algorithms.simple_paths import * |
|
||||||
from networkx.algorithms.smallworld import * |
|
||||||
from networkx.algorithms.smetric import * |
|
||||||
from networkx.algorithms.structuralholes import * |
|
||||||
from networkx.algorithms.sparsifiers import * |
|
||||||
from networkx.algorithms.swap import * |
|
||||||
from networkx.algorithms.traversal import * |
|
||||||
from networkx.algorithms.triads import * |
|
||||||
from networkx.algorithms.vitality import * |
|
||||||
from networkx.algorithms.voronoi import * |
|
||||||
from networkx.algorithms.wiener import * |
|
||||||
|
|
||||||
# Make certain subpackages available to the user as direct imports from |
|
||||||
# the `networkx` namespace. |
|
||||||
import networkx.algorithms.assortativity |
|
||||||
import networkx.algorithms.bipartite |
|
||||||
import networkx.algorithms.node_classification |
|
||||||
import networkx.algorithms.centrality |
|
||||||
import networkx.algorithms.chordal |
|
||||||
import networkx.algorithms.cluster |
|
||||||
import networkx.algorithms.clique |
|
||||||
import networkx.algorithms.components |
|
||||||
import networkx.algorithms.connectivity |
|
||||||
import networkx.algorithms.community |
|
||||||
import networkx.algorithms.coloring |
|
||||||
import networkx.algorithms.flow |
|
||||||
import networkx.algorithms.isomorphism |
|
||||||
import networkx.algorithms.link_analysis |
|
||||||
import networkx.algorithms.lowest_common_ancestors |
|
||||||
import networkx.algorithms.operators |
|
||||||
import networkx.algorithms.shortest_paths |
|
||||||
import networkx.algorithms.tournament |
|
||||||
import networkx.algorithms.traversal |
|
||||||
import networkx.algorithms.tree |
|
||||||
|
|
||||||
# Make certain functions from some of the previous subpackages available |
|
||||||
# to the user as direct imports from the `networkx` namespace. |
|
||||||
from networkx.algorithms.bipartite import complete_bipartite_graph |
|
||||||
from networkx.algorithms.bipartite import is_bipartite |
|
||||||
from networkx.algorithms.bipartite import project |
|
||||||
from networkx.algorithms.bipartite import projected_graph |
|
||||||
from networkx.algorithms.connectivity import all_pairs_node_connectivity |
|
||||||
from networkx.algorithms.connectivity import all_node_cuts |
|
||||||
from networkx.algorithms.connectivity import average_node_connectivity |
|
||||||
from networkx.algorithms.connectivity import edge_connectivity |
|
||||||
from networkx.algorithms.connectivity import edge_disjoint_paths |
|
||||||
from networkx.algorithms.connectivity import k_components |
|
||||||
from networkx.algorithms.connectivity import k_edge_components |
|
||||||
from networkx.algorithms.connectivity import k_edge_subgraphs |
|
||||||
from networkx.algorithms.connectivity import k_edge_augmentation |
|
||||||
from networkx.algorithms.connectivity import is_k_edge_connected |
|
||||||
from networkx.algorithms.connectivity import minimum_edge_cut |
|
||||||
from networkx.algorithms.connectivity import minimum_node_cut |
|
||||||
from networkx.algorithms.connectivity import node_connectivity |
|
||||||
from networkx.algorithms.connectivity import node_disjoint_paths |
|
||||||
from networkx.algorithms.connectivity import stoer_wagner |
|
||||||
from networkx.algorithms.flow import capacity_scaling |
|
||||||
from networkx.algorithms.flow import cost_of_flow |
|
||||||
from networkx.algorithms.flow import gomory_hu_tree |
|
||||||
from networkx.algorithms.flow import max_flow_min_cost |
|
||||||
from networkx.algorithms.flow import maximum_flow |
|
||||||
from networkx.algorithms.flow import maximum_flow_value |
|
||||||
from networkx.algorithms.flow import min_cost_flow |
|
||||||
from networkx.algorithms.flow import min_cost_flow_cost |
|
||||||
from networkx.algorithms.flow import minimum_cut |
|
||||||
from networkx.algorithms.flow import minimum_cut_value |
|
||||||
from networkx.algorithms.flow import network_simplex |
|
||||||
from networkx.algorithms.isomorphism import could_be_isomorphic |
|
||||||
from networkx.algorithms.isomorphism import fast_could_be_isomorphic |
|
||||||
from networkx.algorithms.isomorphism import faster_could_be_isomorphic |
|
||||||
from networkx.algorithms.isomorphism import is_isomorphic |
|
||||||
from networkx.algorithms.tree.branchings import maximum_branching |
|
||||||
from networkx.algorithms.tree.branchings import maximum_spanning_arborescence |
|
||||||
from networkx.algorithms.tree.branchings import minimum_branching |
|
||||||
from networkx.algorithms.tree.branchings import minimum_spanning_arborescence |
|
||||||
from networkx.algorithms.tree.coding import * |
|
||||||
from networkx.algorithms.tree.decomposition import * |
|
||||||
from networkx.algorithms.tree.mst import * |
|
||||||
from networkx.algorithms.tree.operations import * |
|
||||||
from networkx.algorithms.tree.recognition import * |
|
@ -1,22 +0,0 @@ |
|||||||
"""Approximations of graph properties and Heuristic functions for optimization |
|
||||||
problems. |
|
||||||
|
|
||||||
.. warning:: The approximation submodule is not imported in the top-level |
|
||||||
``networkx``. |
|
||||||
|
|
||||||
These functions can be imported with |
|
||||||
``from networkx.algorithms import approximation``. |
|
||||||
|
|
||||||
""" |
|
||||||
|
|
||||||
from networkx.algorithms.approximation.clustering_coefficient import * |
|
||||||
from networkx.algorithms.approximation.clique import * |
|
||||||
from networkx.algorithms.approximation.connectivity import * |
|
||||||
from networkx.algorithms.approximation.dominating_set import * |
|
||||||
from networkx.algorithms.approximation.kcomponents import * |
|
||||||
from networkx.algorithms.approximation.independent_set import * |
|
||||||
from networkx.algorithms.approximation.matching import * |
|
||||||
from networkx.algorithms.approximation.ramsey import * |
|
||||||
from networkx.algorithms.approximation.steinertree import * |
|
||||||
from networkx.algorithms.approximation.vertex_cover import * |
|
||||||
from networkx.algorithms.approximation.treewidth import * |
|
@ -1,159 +0,0 @@ |
|||||||
"""Functions for computing large cliques.""" |
|
||||||
import networkx as nx |
|
||||||
from networkx.utils import not_implemented_for |
|
||||||
from networkx.algorithms.approximation import ramsey |
|
||||||
|
|
||||||
__all__ = ["clique_removal", "max_clique", "large_clique_size"] |
|
||||||
|
|
||||||
|
|
||||||
def max_clique(G): |
|
||||||
r"""Find the Maximum Clique |
|
||||||
|
|
||||||
Finds the $O(|V|/(log|V|)^2)$ apx of maximum clique/independent set |
|
||||||
in the worst case. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
Undirected graph |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
clique : set |
|
||||||
The apx-maximum clique of the graph |
|
||||||
|
|
||||||
Notes |
|
||||||
------ |
|
||||||
A clique in an undirected graph G = (V, E) is a subset of the vertex set |
|
||||||
`C \subseteq V` such that for every two vertices in C there exists an edge |
|
||||||
connecting the two. This is equivalent to saying that the subgraph |
|
||||||
induced by C is complete (in some cases, the term clique may also refer |
|
||||||
to the subgraph). |
|
||||||
|
|
||||||
A maximum clique is a clique of the largest possible size in a given graph. |
|
||||||
The clique number `\omega(G)` of a graph G is the number of |
|
||||||
vertices in a maximum clique in G. The intersection number of |
|
||||||
G is the smallest number of cliques that together cover all edges of G. |
|
||||||
|
|
||||||
https://en.wikipedia.org/wiki/Maximum_clique |
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] Boppana, R., & Halldórsson, M. M. (1992). |
|
||||||
Approximating maximum independent sets by excluding subgraphs. |
|
||||||
BIT Numerical Mathematics, 32(2), 180–196. Springer. |
|
||||||
doi:10.1007/BF01994876 |
|
||||||
""" |
|
||||||
if G is None: |
|
||||||
raise ValueError("Expected NetworkX graph!") |
|
||||||
|
|
||||||
# finding the maximum clique in a graph is equivalent to finding |
|
||||||
# the independent set in the complementary graph |
|
||||||
cgraph = nx.complement(G) |
|
||||||
iset, _ = clique_removal(cgraph) |
|
||||||
return iset |
|
||||||
|
|
||||||
|
|
||||||
def clique_removal(G): |
|
||||||
r""" Repeatedly remove cliques from the graph. |
|
||||||
|
|
||||||
Results in a $O(|V|/(\log |V|)^2)$ approximation of maximum clique |
|
||||||
and independent set. Returns the largest independent set found, along |
|
||||||
with found maximal cliques. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
Undirected graph |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
max_ind_cliques : (set, list) tuple |
|
||||||
2-tuple of Maximal Independent Set and list of maximal cliques (sets). |
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] Boppana, R., & Halldórsson, M. M. (1992). |
|
||||||
Approximating maximum independent sets by excluding subgraphs. |
|
||||||
BIT Numerical Mathematics, 32(2), 180–196. Springer. |
|
||||||
""" |
|
||||||
graph = G.copy() |
|
||||||
c_i, i_i = ramsey.ramsey_R2(graph) |
|
||||||
cliques = [c_i] |
|
||||||
isets = [i_i] |
|
||||||
while graph: |
|
||||||
graph.remove_nodes_from(c_i) |
|
||||||
c_i, i_i = ramsey.ramsey_R2(graph) |
|
||||||
if c_i: |
|
||||||
cliques.append(c_i) |
|
||||||
if i_i: |
|
||||||
isets.append(i_i) |
|
||||||
# Determine the largest independent set as measured by cardinality. |
|
||||||
maxiset = max(isets, key=len) |
|
||||||
return maxiset, cliques |
|
||||||
|
|
||||||
|
|
||||||
@not_implemented_for("directed") |
|
||||||
@not_implemented_for("multigraph") |
|
||||||
def large_clique_size(G): |
|
||||||
"""Find the size of a large clique in a graph. |
|
||||||
|
|
||||||
A *clique* is a subset of nodes in which each pair of nodes is |
|
||||||
adjacent. This function is a heuristic for finding the size of a |
|
||||||
large clique in the graph. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
int |
|
||||||
The size of a large clique in the graph. |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
This implementation is from [1]_. Its worst case time complexity is |
|
||||||
:math:`O(n d^2)`, where *n* is the number of nodes in the graph and |
|
||||||
*d* is the maximum degree. |
|
||||||
|
|
||||||
This function is a heuristic, which means it may work well in |
|
||||||
practice, but there is no rigorous mathematical guarantee on the |
|
||||||
ratio between the returned number and the actual largest clique size |
|
||||||
in the graph. |
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] Pattabiraman, Bharath, et al. |
|
||||||
"Fast Algorithms for the Maximum Clique Problem on Massive Graphs |
|
||||||
with Applications to Overlapping Community Detection." |
|
||||||
*Internet Mathematics* 11.4-5 (2015): 421--448. |
|
||||||
<https://doi.org/10.1080/15427951.2014.986778> |
|
||||||
|
|
||||||
See also |
|
||||||
-------- |
|
||||||
|
|
||||||
:func:`networkx.algorithms.approximation.clique.max_clique` |
|
||||||
A function that returns an approximate maximum clique with a |
|
||||||
guarantee on the approximation ratio. |
|
||||||
|
|
||||||
:mod:`networkx.algorithms.clique` |
|
||||||
Functions for finding the exact maximum clique in a graph. |
|
||||||
|
|
||||||
""" |
|
||||||
degrees = G.degree |
|
||||||
|
|
||||||
def _clique_heuristic(G, U, size, best_size): |
|
||||||
if not U: |
|
||||||
return max(best_size, size) |
|
||||||
u = max(U, key=degrees) |
|
||||||
U.remove(u) |
|
||||||
N_prime = {v for v in G[u] if degrees[v] >= best_size} |
|
||||||
return _clique_heuristic(G, U & N_prime, size + 1, best_size) |
|
||||||
|
|
||||||
best_size = 0 |
|
||||||
nodes = (u for u in G if degrees[u] >= best_size) |
|
||||||
for u in nodes: |
|
||||||
neighbors = {v for v in G[u] if degrees[v] >= best_size} |
|
||||||
best_size = _clique_heuristic(G, neighbors, 1, best_size) |
|
||||||
return best_size |
|
@ -1,58 +0,0 @@ |
|||||||
from networkx.utils import not_implemented_for |
|
||||||
from networkx.utils import py_random_state |
|
||||||
|
|
||||||
__all__ = ["average_clustering"] |
|
||||||
|
|
||||||
|
|
||||||
@py_random_state(2) |
|
||||||
@not_implemented_for("directed") |
|
||||||
def average_clustering(G, trials=1000, seed=None): |
|
||||||
r"""Estimates the average clustering coefficient of G. |
|
||||||
|
|
||||||
The local clustering of each node in `G` is the fraction of triangles |
|
||||||
that actually exist over all possible triangles in its neighborhood. |
|
||||||
The average clustering coefficient of a graph `G` is the mean of |
|
||||||
local clusterings. |
|
||||||
|
|
||||||
This function finds an approximate average clustering coefficient |
|
||||||
for G by repeating `n` times (defined in `trials`) the following |
|
||||||
experiment: choose a node at random, choose two of its neighbors |
|
||||||
at random, and check if they are connected. The approximate |
|
||||||
coefficient is the fraction of triangles found over the number |
|
||||||
of trials [1]_. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
|
|
||||||
trials : integer |
|
||||||
Number of trials to perform (default 1000). |
|
||||||
|
|
||||||
seed : integer, random_state, or None (default) |
|
||||||
Indicator of random number generation state. |
|
||||||
See :ref:`Randomness<randomness>`. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
c : float |
|
||||||
Approximated average clustering coefficient. |
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] Schank, Thomas, and Dorothea Wagner. Approximating clustering |
|
||||||
coefficient and transitivity. Universität Karlsruhe, Fakultät für |
|
||||||
Informatik, 2004. |
|
||||||
http://www.emis.ams.org/journals/JGAA/accepted/2005/SchankWagner2005.9.2.pdf |
|
||||||
|
|
||||||
""" |
|
||||||
n = len(G) |
|
||||||
triangles = 0 |
|
||||||
nodes = list(G) |
|
||||||
for i in [int(seed.random() * n) for i in range(trials)]: |
|
||||||
nbrs = list(G[nodes[i]]) |
|
||||||
if len(nbrs) < 2: |
|
||||||
continue |
|
||||||
u, v = seed.sample(nbrs, 2) |
|
||||||
if u in G[v]: |
|
||||||
triangles += 1 |
|
||||||
return triangles / float(trials) |
|
@ -1,403 +0,0 @@ |
|||||||
""" Fast approximation for node connectivity |
|
||||||
""" |
|
||||||
import itertools |
|
||||||
from operator import itemgetter |
|
||||||
|
|
||||||
import networkx as nx |
|
||||||
|
|
||||||
__all__ = [ |
|
||||||
"local_node_connectivity", |
|
||||||
"node_connectivity", |
|
||||||
"all_pairs_node_connectivity", |
|
||||||
] |
|
||||||
|
|
||||||
INF = float("inf") |
|
||||||
|
|
||||||
|
|
||||||
def local_node_connectivity(G, source, target, cutoff=None): |
|
||||||
"""Compute node connectivity between source and target. |
|
||||||
|
|
||||||
Pairwise or local node connectivity between two distinct and nonadjacent |
|
||||||
nodes is the minimum number of nodes that must be removed (minimum |
|
||||||
separating cutset) to disconnect them. By Menger's theorem, this is equal |
|
||||||
to the number of node independent paths (paths that share no nodes other |
|
||||||
than source and target). Which is what we compute in this function. |
|
||||||
|
|
||||||
This algorithm is a fast approximation that gives an strict lower |
|
||||||
bound on the actual number of node independent paths between two nodes [1]_. |
|
||||||
It works for both directed and undirected graphs. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
|
|
||||||
G : NetworkX graph |
|
||||||
|
|
||||||
source : node |
|
||||||
Starting node for node connectivity |
|
||||||
|
|
||||||
target : node |
|
||||||
Ending node for node connectivity |
|
||||||
|
|
||||||
cutoff : integer |
|
||||||
Maximum node connectivity to consider. If None, the minimum degree |
|
||||||
of source or target is used as a cutoff. Default value None. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
k: integer |
|
||||||
pairwise node connectivity |
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
>>> # Platonic octahedral graph has node connectivity 4 |
|
||||||
>>> # for each non adjacent node pair |
|
||||||
>>> from networkx.algorithms import approximation as approx |
|
||||||
>>> G = nx.octahedral_graph() |
|
||||||
>>> approx.local_node_connectivity(G, 0, 5) |
|
||||||
4 |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
This algorithm [1]_ finds node independents paths between two nodes by |
|
||||||
computing their shortest path using BFS, marking the nodes of the path |
|
||||||
found as 'used' and then searching other shortest paths excluding the |
|
||||||
nodes marked as used until no more paths exist. It is not exact because |
|
||||||
a shortest path could use nodes that, if the path were longer, may belong |
|
||||||
to two different node independent paths. Thus it only guarantees an |
|
||||||
strict lower bound on node connectivity. |
|
||||||
|
|
||||||
Note that the authors propose a further refinement, losing accuracy and |
|
||||||
gaining speed, which is not implemented yet. |
|
||||||
|
|
||||||
See also |
|
||||||
-------- |
|
||||||
all_pairs_node_connectivity |
|
||||||
node_connectivity |
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] White, Douglas R., and Mark Newman. 2001 A Fast Algorithm for |
|
||||||
Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035 |
|
||||||
http://eclectic.ss.uci.edu/~drwhite/working.pdf |
|
||||||
|
|
||||||
""" |
|
||||||
if target == source: |
|
||||||
raise nx.NetworkXError("source and target have to be different nodes.") |
|
||||||
|
|
||||||
# Maximum possible node independent paths |
|
||||||
if G.is_directed(): |
|
||||||
possible = min(G.out_degree(source), G.in_degree(target)) |
|
||||||
else: |
|
||||||
possible = min(G.degree(source), G.degree(target)) |
|
||||||
|
|
||||||
K = 0 |
|
||||||
if not possible: |
|
||||||
return K |
|
||||||
|
|
||||||
if cutoff is None: |
|
||||||
cutoff = INF |
|
||||||
|
|
||||||
exclude = set() |
|
||||||
for i in range(min(possible, cutoff)): |
|
||||||
try: |
|
||||||
path = _bidirectional_shortest_path(G, source, target, exclude) |
|
||||||
exclude.update(set(path)) |
|
||||||
K += 1 |
|
||||||
except nx.NetworkXNoPath: |
|
||||||
break |
|
||||||
|
|
||||||
return K |
|
||||||
|
|
||||||
|
|
||||||
def node_connectivity(G, s=None, t=None): |
|
||||||
r"""Returns an approximation for node connectivity for a graph or digraph G. |
|
||||||
|
|
||||||
Node connectivity is equal to the minimum number of nodes that |
|
||||||
must be removed to disconnect G or render it trivial. By Menger's theorem, |
|
||||||
this is equal to the number of node independent paths (paths that |
|
||||||
share no nodes other than source and target). |
|
||||||
|
|
||||||
If source and target nodes are provided, this function returns the |
|
||||||
local node connectivity: the minimum number of nodes that must be |
|
||||||
removed to break all paths from source to target in G. |
|
||||||
|
|
||||||
This algorithm is based on a fast approximation that gives an strict lower |
|
||||||
bound on the actual number of node independent paths between two nodes [1]_. |
|
||||||
It works for both directed and undirected graphs. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
Undirected graph |
|
||||||
|
|
||||||
s : node |
|
||||||
Source node. Optional. Default value: None. |
|
||||||
|
|
||||||
t : node |
|
||||||
Target node. Optional. Default value: None. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
K : integer |
|
||||||
Node connectivity of G, or local node connectivity if source |
|
||||||
and target are provided. |
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
>>> # Platonic octahedral graph is 4-node-connected |
|
||||||
>>> from networkx.algorithms import approximation as approx |
|
||||||
>>> G = nx.octahedral_graph() |
|
||||||
>>> approx.node_connectivity(G) |
|
||||||
4 |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
This algorithm [1]_ finds node independents paths between two nodes by |
|
||||||
computing their shortest path using BFS, marking the nodes of the path |
|
||||||
found as 'used' and then searching other shortest paths excluding the |
|
||||||
nodes marked as used until no more paths exist. It is not exact because |
|
||||||
a shortest path could use nodes that, if the path were longer, may belong |
|
||||||
to two different node independent paths. Thus it only guarantees an |
|
||||||
strict lower bound on node connectivity. |
|
||||||
|
|
||||||
See also |
|
||||||
-------- |
|
||||||
all_pairs_node_connectivity |
|
||||||
local_node_connectivity |
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] White, Douglas R., and Mark Newman. 2001 A Fast Algorithm for |
|
||||||
Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035 |
|
||||||
http://eclectic.ss.uci.edu/~drwhite/working.pdf |
|
||||||
|
|
||||||
""" |
|
||||||
if (s is not None and t is None) or (s is None and t is not None): |
|
||||||
raise nx.NetworkXError("Both source and target must be specified.") |
|
||||||
|
|
||||||
# Local node connectivity |
|
||||||
if s is not None and t is not None: |
|
||||||
if s not in G: |
|
||||||
raise nx.NetworkXError(f"node {s} not in graph") |
|
||||||
if t not in G: |
|
||||||
raise nx.NetworkXError(f"node {t} not in graph") |
|
||||||
return local_node_connectivity(G, s, t) |
|
||||||
|
|
||||||
# Global node connectivity |
|
||||||
if G.is_directed(): |
|
||||||
connected_func = nx.is_weakly_connected |
|
||||||
iter_func = itertools.permutations |
|
||||||
|
|
||||||
def neighbors(v): |
|
||||||
return itertools.chain(G.predecessors(v), G.successors(v)) |
|
||||||
|
|
||||||
else: |
|
||||||
connected_func = nx.is_connected |
|
||||||
iter_func = itertools.combinations |
|
||||||
neighbors = G.neighbors |
|
||||||
|
|
||||||
if not connected_func(G): |
|
||||||
return 0 |
|
||||||
|
|
||||||
# Choose a node with minimum degree |
|
||||||
v, minimum_degree = min(G.degree(), key=itemgetter(1)) |
|
||||||
# Node connectivity is bounded by minimum degree |
|
||||||
K = minimum_degree |
|
||||||
# compute local node connectivity with all non-neighbors nodes |
|
||||||
# and store the minimum |
|
||||||
for w in set(G) - set(neighbors(v)) - {v}: |
|
||||||
K = min(K, local_node_connectivity(G, v, w, cutoff=K)) |
|
||||||
# Same for non adjacent pairs of neighbors of v |
|
||||||
for x, y in iter_func(neighbors(v), 2): |
|
||||||
if y not in G[x] and x != y: |
|
||||||
K = min(K, local_node_connectivity(G, x, y, cutoff=K)) |
|
||||||
return K |
|
||||||
|
|
||||||
|
|
||||||
def all_pairs_node_connectivity(G, nbunch=None, cutoff=None): |
|
||||||
""" Compute node connectivity between all pairs of nodes. |
|
||||||
|
|
||||||
Pairwise or local node connectivity between two distinct and nonadjacent |
|
||||||
nodes is the minimum number of nodes that must be removed (minimum |
|
||||||
separating cutset) to disconnect them. By Menger's theorem, this is equal |
|
||||||
to the number of node independent paths (paths that share no nodes other |
|
||||||
than source and target). Which is what we compute in this function. |
|
||||||
|
|
||||||
This algorithm is a fast approximation that gives an strict lower |
|
||||||
bound on the actual number of node independent paths between two nodes [1]_. |
|
||||||
It works for both directed and undirected graphs. |
|
||||||
|
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
|
|
||||||
nbunch: container |
|
||||||
Container of nodes. If provided node connectivity will be computed |
|
||||||
only over pairs of nodes in nbunch. |
|
||||||
|
|
||||||
cutoff : integer |
|
||||||
Maximum node connectivity to consider. If None, the minimum degree |
|
||||||
of source or target is used as a cutoff in each pair of nodes. |
|
||||||
Default value None. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
K : dictionary |
|
||||||
Dictionary, keyed by source and target, of pairwise node connectivity |
|
||||||
|
|
||||||
See Also |
|
||||||
-------- |
|
||||||
local_node_connectivity |
|
||||||
node_connectivity |
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] White, Douglas R., and Mark Newman. 2001 A Fast Algorithm for |
|
||||||
Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035 |
|
||||||
http://eclectic.ss.uci.edu/~drwhite/working.pdf |
|
||||||
""" |
|
||||||
if nbunch is None: |
|
||||||
nbunch = G |
|
||||||
else: |
|
||||||
nbunch = set(nbunch) |
|
||||||
|
|
||||||
directed = G.is_directed() |
|
||||||
if directed: |
|
||||||
iter_func = itertools.permutations |
|
||||||
else: |
|
||||||
iter_func = itertools.combinations |
|
||||||
|
|
||||||
all_pairs = {n: {} for n in nbunch} |
|
||||||
|
|
||||||
for u, v in iter_func(nbunch, 2): |
|
||||||
k = local_node_connectivity(G, u, v, cutoff=cutoff) |
|
||||||
all_pairs[u][v] = k |
|
||||||
if not directed: |
|
||||||
all_pairs[v][u] = k |
|
||||||
|
|
||||||
return all_pairs |
|
||||||
|
|
||||||
|
|
||||||
def _bidirectional_shortest_path(G, source, target, exclude): |
|
||||||
"""Returns shortest path between source and target ignoring nodes in the |
|
||||||
container 'exclude'. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
|
|
||||||
G : NetworkX graph |
|
||||||
|
|
||||||
source : node |
|
||||||
Starting node for path |
|
||||||
|
|
||||||
target : node |
|
||||||
Ending node for path |
|
||||||
|
|
||||||
exclude: container |
|
||||||
Container for nodes to exclude from the search for shortest paths |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
path: list |
|
||||||
Shortest path between source and target ignoring nodes in 'exclude' |
|
||||||
|
|
||||||
Raises |
|
||||||
------ |
|
||||||
NetworkXNoPath |
|
||||||
If there is no path or if nodes are adjacent and have only one path |
|
||||||
between them |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
This function and its helper are originally from |
|
||||||
networkx.algorithms.shortest_paths.unweighted and are modified to |
|
||||||
accept the extra parameter 'exclude', which is a container for nodes |
|
||||||
already used in other paths that should be ignored. |
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] White, Douglas R., and Mark Newman. 2001 A Fast Algorithm for |
|
||||||
Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035 |
|
||||||
http://eclectic.ss.uci.edu/~drwhite/working.pdf |
|
||||||
|
|
||||||
""" |
|
||||||
# call helper to do the real work |
|
||||||
results = _bidirectional_pred_succ(G, source, target, exclude) |
|
||||||
pred, succ, w = results |
|
||||||
|
|
||||||
# build path from pred+w+succ |
|
||||||
path = [] |
|
||||||
# from source to w |
|
||||||
while w is not None: |
|
||||||
path.append(w) |
|
||||||
w = pred[w] |
|
||||||
path.reverse() |
|
||||||
# from w to target |
|
||||||
w = succ[path[-1]] |
|
||||||
while w is not None: |
|
||||||
path.append(w) |
|
||||||
w = succ[w] |
|
||||||
|
|
||||||
return path |
|
||||||
|
|
||||||
|
|
||||||
def _bidirectional_pred_succ(G, source, target, exclude): |
|
||||||
# does BFS from both source and target and meets in the middle |
|
||||||
# excludes nodes in the container "exclude" from the search |
|
||||||
if source is None or target is None: |
|
||||||
raise nx.NetworkXException( |
|
||||||
"Bidirectional shortest path called without source or target" |
|
||||||
) |
|
||||||
if target == source: |
|
||||||
return ({target: None}, {source: None}, source) |
|
||||||
|
|
||||||
# handle either directed or undirected |
|
||||||
if G.is_directed(): |
|
||||||
Gpred = G.predecessors |
|
||||||
Gsucc = G.successors |
|
||||||
else: |
|
||||||
Gpred = G.neighbors |
|
||||||
Gsucc = G.neighbors |
|
||||||
|
|
||||||
# predecesssor and successors in search |
|
||||||
pred = {source: None} |
|
||||||
succ = {target: None} |
|
||||||
|
|
||||||
# initialize fringes, start with forward |
|
||||||
forward_fringe = [source] |
|
||||||
reverse_fringe = [target] |
|
||||||
|
|
||||||
level = 0 |
|
||||||
|
|
||||||
while forward_fringe and reverse_fringe: |
|
||||||
# Make sure that we iterate one step forward and one step backwards |
|
||||||
# thus source and target will only trigger "found path" when they are |
|
||||||
# adjacent and then they can be safely included in the container 'exclude' |
|
||||||
level += 1 |
|
||||||
if not level % 2 == 0: |
|
||||||
this_level = forward_fringe |
|
||||||
forward_fringe = [] |
|
||||||
for v in this_level: |
|
||||||
for w in Gsucc(v): |
|
||||||
if w in exclude: |
|
||||||
continue |
|
||||||
if w not in pred: |
|
||||||
forward_fringe.append(w) |
|
||||||
pred[w] = v |
|
||||||
if w in succ: |
|
||||||
return pred, succ, w # found path |
|
||||||
else: |
|
||||||
this_level = reverse_fringe |
|
||||||
reverse_fringe = [] |
|
||||||
for v in this_level: |
|
||||||
for w in Gpred(v): |
|
||||||
if w in exclude: |
|
||||||
continue |
|
||||||
if w not in succ: |
|
||||||
succ[w] = v |
|
||||||
reverse_fringe.append(w) |
|
||||||
if w in pred: |
|
||||||
return pred, succ, w # found path |
|
||||||
|
|
||||||
raise nx.NetworkXNoPath(f"No path between {source} and {target}.") |
|
@ -1,123 +0,0 @@ |
|||||||
"""Functions for finding node and edge dominating sets. |
|
||||||
|
|
||||||
A `dominating set`_ for an undirected graph *G* with vertex set *V* |
|
||||||
and edge set *E* is a subset *D* of *V* such that every vertex not in |
|
||||||
*D* is adjacent to at least one member of *D*. An `edge dominating set`_ |
|
||||||
is a subset *F* of *E* such that every edge not in *F* is |
|
||||||
incident to an endpoint of at least one edge in *F*. |
|
||||||
|
|
||||||
.. _dominating set: https://en.wikipedia.org/wiki/Dominating_set |
|
||||||
.. _edge dominating set: https://en.wikipedia.org/wiki/Edge_dominating_set |
|
||||||
|
|
||||||
""" |
|
||||||
|
|
||||||
from ..matching import maximal_matching |
|
||||||
from ...utils import not_implemented_for |
|
||||||
|
|
||||||
__all__ = ["min_weighted_dominating_set", "min_edge_dominating_set"] |
|
||||||
|
|
||||||
|
|
||||||
# TODO Why doesn't this algorithm work for directed graphs? |
|
||||||
@not_implemented_for("directed") |
|
||||||
def min_weighted_dominating_set(G, weight=None): |
|
||||||
r"""Returns a dominating set that approximates the minimum weight node |
|
||||||
dominating set. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
Undirected graph. |
|
||||||
|
|
||||||
weight : string |
|
||||||
The node attribute storing the weight of an node. If provided, |
|
||||||
the node attribute with this key must be a number for each |
|
||||||
node. If not provided, each node is assumed to have weight one. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
min_weight_dominating_set : set |
|
||||||
A set of nodes, the sum of whose weights is no more than `(\log |
|
||||||
w(V)) w(V^*)`, where `w(V)` denotes the sum of the weights of |
|
||||||
each node in the graph and `w(V^*)` denotes the sum of the |
|
||||||
weights of each node in the minimum weight dominating set. |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
This algorithm computes an approximate minimum weighted dominating |
|
||||||
set for the graph `G`. The returned solution has weight `(\log |
|
||||||
w(V)) w(V^*)`, where `w(V)` denotes the sum of the weights of each |
|
||||||
node in the graph and `w(V^*)` denotes the sum of the weights of |
|
||||||
each node in the minimum weight dominating set for the graph. |
|
||||||
|
|
||||||
This implementation of the algorithm runs in $O(m)$ time, where $m$ |
|
||||||
is the number of edges in the graph. |
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] Vazirani, Vijay V. |
|
||||||
*Approximation Algorithms*. |
|
||||||
Springer Science & Business Media, 2001. |
|
||||||
|
|
||||||
""" |
|
||||||
# The unique dominating set for the null graph is the empty set. |
|
||||||
if len(G) == 0: |
|
||||||
return set() |
|
||||||
|
|
||||||
# This is the dominating set that will eventually be returned. |
|
||||||
dom_set = set() |
|
||||||
|
|
||||||
def _cost(node_and_neighborhood): |
|
||||||
"""Returns the cost-effectiveness of greedily choosing the given |
|
||||||
node. |
|
||||||
|
|
||||||
`node_and_neighborhood` is a two-tuple comprising a node and its |
|
||||||
closed neighborhood. |
|
||||||
|
|
||||||
""" |
|
||||||
v, neighborhood = node_and_neighborhood |
|
||||||
return G.nodes[v].get(weight, 1) / len(neighborhood - dom_set) |
|
||||||
|
|
||||||
# This is a set of all vertices not already covered by the |
|
||||||
# dominating set. |
|
||||||
vertices = set(G) |
|
||||||
# This is a dictionary mapping each node to the closed neighborhood |
|
||||||
# of that node. |
|
||||||
neighborhoods = {v: {v} | set(G[v]) for v in G} |
|
||||||
|
|
||||||
# Continue until all vertices are adjacent to some node in the |
|
||||||
# dominating set. |
|
||||||
while vertices: |
|
||||||
# Find the most cost-effective node to add, along with its |
|
||||||
# closed neighborhood. |
|
||||||
dom_node, min_set = min(neighborhoods.items(), key=_cost) |
|
||||||
# Add the node to the dominating set and reduce the remaining |
|
||||||
# set of nodes to cover. |
|
||||||
dom_set.add(dom_node) |
|
||||||
del neighborhoods[dom_node] |
|
||||||
vertices -= min_set |
|
||||||
|
|
||||||
return dom_set |
|
||||||
|
|
||||||
|
|
||||||
def min_edge_dominating_set(G): |
|
||||||
r"""Returns minimum cardinality edge dominating set. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
Undirected graph |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
min_edge_dominating_set : set |
|
||||||
Returns a set of dominating edges whose size is no more than 2 * OPT. |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
The algorithm computes an approximate solution to the edge dominating set |
|
||||||
problem. The result is no more than 2 * OPT in terms of size of the set. |
|
||||||
Runtime of the algorithm is $O(|E|)$. |
|
||||||
""" |
|
||||||
if not G: |
|
||||||
raise ValueError("Expected non-empty NetworkX graph!") |
|
||||||
return maximal_matching(G) |
|
@ -1,58 +0,0 @@ |
|||||||
r""" |
|
||||||
Independent Set |
|
||||||
|
|
||||||
Independent set or stable set is a set of vertices in a graph, no two of |
|
||||||
which are adjacent. That is, it is a set I of vertices such that for every |
|
||||||
two vertices in I, there is no edge connecting the two. Equivalently, each |
|
||||||
edge in the graph has at most one endpoint in I. The size of an independent |
|
||||||
set is the number of vertices it contains. |
|
||||||
|
|
||||||
A maximum independent set is a largest independent set for a given graph G |
|
||||||
and its size is denoted $\alpha(G)$. The problem of finding such a set is called |
|
||||||
the maximum independent set problem and is an NP-hard optimization problem. |
|
||||||
As such, it is unlikely that there exists an efficient algorithm for finding |
|
||||||
a maximum independent set of a graph. |
|
||||||
|
|
||||||
`Wikipedia: Independent set <https://en.wikipedia.org/wiki/Independent_set_(graph_theory)>`_ |
|
||||||
|
|
||||||
Independent set algorithm is based on the following paper: |
|
||||||
|
|
||||||
$O(|V|/(log|V|)^2)$ apx of maximum clique/independent set. |
|
||||||
|
|
||||||
Boppana, R., & Halldórsson, M. M. (1992). |
|
||||||
Approximating maximum independent sets by excluding subgraphs. |
|
||||||
BIT Numerical Mathematics, 32(2), 180–196. Springer. |
|
||||||
doi:10.1007/BF01994876 |
|
||||||
|
|
||||||
""" |
|
||||||
from networkx.algorithms.approximation import clique_removal |
|
||||||
|
|
||||||
__all__ = ["maximum_independent_set"] |
|
||||||
|
|
||||||
|
|
||||||
def maximum_independent_set(G): |
|
||||||
"""Returns an approximate maximum independent set. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
Undirected graph |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
iset : Set |
|
||||||
The apx-maximum independent set |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
Finds the $O(|V|/(log|V|)^2)$ apx of independent set in the worst case. |
|
||||||
|
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] Boppana, R., & Halldórsson, M. M. (1992). |
|
||||||
Approximating maximum independent sets by excluding subgraphs. |
|
||||||
BIT Numerical Mathematics, 32(2), 180–196. Springer. |
|
||||||
""" |
|
||||||
iset, _ = clique_removal(G) |
|
||||||
return iset |
|
@ -1,368 +0,0 @@ |
|||||||
""" Fast approximation for k-component structure |
|
||||||
""" |
|
||||||
import itertools |
|
||||||
from collections import defaultdict |
|
||||||
from collections.abc import Mapping |
|
||||||
|
|
||||||
import networkx as nx |
|
||||||
from networkx.exception import NetworkXError |
|
||||||
from networkx.utils import not_implemented_for |
|
||||||
|
|
||||||
from networkx.algorithms.approximation import local_node_connectivity |
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["k_components"] |
|
||||||
|
|
||||||
|
|
||||||
not_implemented_for("directed") |
|
||||||
|
|
||||||
|
|
||||||
def k_components(G, min_density=0.95): |
|
||||||
r"""Returns the approximate k-component structure of a graph G. |
|
||||||
|
|
||||||
A `k`-component is a maximal subgraph of a graph G that has, at least, |
|
||||||
node connectivity `k`: we need to remove at least `k` nodes to break it |
|
||||||
into more components. `k`-components have an inherent hierarchical |
|
||||||
structure because they are nested in terms of connectivity: a connected |
|
||||||
graph can contain several 2-components, each of which can contain |
|
||||||
one or more 3-components, and so forth. |
|
||||||
|
|
||||||
This implementation is based on the fast heuristics to approximate |
|
||||||
the `k`-component structure of a graph [1]_. Which, in turn, it is based on |
|
||||||
a fast approximation algorithm for finding good lower bounds of the number |
|
||||||
of node independent paths between two nodes [2]_. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
Undirected graph |
|
||||||
|
|
||||||
min_density : Float |
|
||||||
Density relaxation threshold. Default value 0.95 |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
k_components : dict |
|
||||||
Dictionary with connectivity level `k` as key and a list of |
|
||||||
sets of nodes that form a k-component of level `k` as values. |
|
||||||
|
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
>>> # Petersen graph has 10 nodes and it is triconnected, thus all |
|
||||||
>>> # nodes are in a single component on all three connectivity levels |
|
||||||
>>> from networkx.algorithms import approximation as apxa |
|
||||||
>>> G = nx.petersen_graph() |
|
||||||
>>> k_components = apxa.k_components(G) |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
The logic of the approximation algorithm for computing the `k`-component |
|
||||||
structure [1]_ is based on repeatedly applying simple and fast algorithms |
|
||||||
for `k`-cores and biconnected components in order to narrow down the |
|
||||||
number of pairs of nodes over which we have to compute White and Newman's |
|
||||||
approximation algorithm for finding node independent paths [2]_. More |
|
||||||
formally, this algorithm is based on Whitney's theorem, which states |
|
||||||
an inclusion relation among node connectivity, edge connectivity, and |
|
||||||
minimum degree for any graph G. This theorem implies that every |
|
||||||
`k`-component is nested inside a `k`-edge-component, which in turn, |
|
||||||
is contained in a `k`-core. Thus, this algorithm computes node independent |
|
||||||
paths among pairs of nodes in each biconnected part of each `k`-core, |
|
||||||
and repeats this procedure for each `k` from 3 to the maximal core number |
|
||||||
of a node in the input graph. |
|
||||||
|
|
||||||
Because, in practice, many nodes of the core of level `k` inside a |
|
||||||
bicomponent actually are part of a component of level k, the auxiliary |
|
||||||
graph needed for the algorithm is likely to be very dense. Thus, we use |
|
||||||
a complement graph data structure (see `AntiGraph`) to save memory. |
|
||||||
AntiGraph only stores information of the edges that are *not* present |
|
||||||
in the actual auxiliary graph. When applying algorithms to this |
|
||||||
complement graph data structure, it behaves as if it were the dense |
|
||||||
version. |
|
||||||
|
|
||||||
See also |
|
||||||
-------- |
|
||||||
k_components |
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] Torrents, J. and F. Ferraro (2015) Structural Cohesion: |
|
||||||
Visualization and Heuristics for Fast Computation. |
|
||||||
https://arxiv.org/pdf/1503.04476v1 |
|
||||||
|
|
||||||
.. [2] White, Douglas R., and Mark Newman (2001) A Fast Algorithm for |
|
||||||
Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035 |
|
||||||
http://eclectic.ss.uci.edu/~drwhite/working.pdf |
|
||||||
|
|
||||||
.. [3] Moody, J. and D. White (2003). Social cohesion and embeddedness: |
|
||||||
A hierarchical conception of social groups. |
|
||||||
American Sociological Review 68(1), 103--28. |
|
||||||
http://www2.asanet.org/journals/ASRFeb03MoodyWhite.pdf |
|
||||||
|
|
||||||
""" |
|
||||||
# Dictionary with connectivity level (k) as keys and a list of |
|
||||||
# sets of nodes that form a k-component as values |
|
||||||
k_components = defaultdict(list) |
|
||||||
# make a few functions local for speed |
|
||||||
node_connectivity = local_node_connectivity |
|
||||||
k_core = nx.k_core |
|
||||||
core_number = nx.core_number |
|
||||||
biconnected_components = nx.biconnected_components |
|
||||||
density = nx.density |
|
||||||
combinations = itertools.combinations |
|
||||||
# Exact solution for k = {1,2} |
|
||||||
# There is a linear time algorithm for triconnectivity, if we had an |
|
||||||
# implementation available we could start from k = 4. |
|
||||||
for component in nx.connected_components(G): |
|
||||||
# isolated nodes have connectivity 0 |
|
||||||
comp = set(component) |
|
||||||
if len(comp) > 1: |
|
||||||
k_components[1].append(comp) |
|
||||||
for bicomponent in nx.biconnected_components(G): |
|
||||||
# avoid considering dyads as bicomponents |
|
||||||
bicomp = set(bicomponent) |
|
||||||
if len(bicomp) > 2: |
|
||||||
k_components[2].append(bicomp) |
|
||||||
# There is no k-component of k > maximum core number |
|
||||||
# \kappa(G) <= \lambda(G) <= \delta(G) |
|
||||||
g_cnumber = core_number(G) |
|
||||||
max_core = max(g_cnumber.values()) |
|
||||||
for k in range(3, max_core + 1): |
|
||||||
C = k_core(G, k, core_number=g_cnumber) |
|
||||||
for nodes in biconnected_components(C): |
|
||||||
# Build a subgraph SG induced by the nodes that are part of |
|
||||||
# each biconnected component of the k-core subgraph C. |
|
||||||
if len(nodes) < k: |
|
||||||
continue |
|
||||||
SG = G.subgraph(nodes) |
|
||||||
# Build auxiliary graph |
|
||||||
H = _AntiGraph() |
|
||||||
H.add_nodes_from(SG.nodes()) |
|
||||||
for u, v in combinations(SG, 2): |
|
||||||
K = node_connectivity(SG, u, v, cutoff=k) |
|
||||||
if k > K: |
|
||||||
H.add_edge(u, v) |
|
||||||
for h_nodes in biconnected_components(H): |
|
||||||
if len(h_nodes) <= k: |
|
||||||
continue |
|
||||||
SH = H.subgraph(h_nodes) |
|
||||||
for Gc in _cliques_heuristic(SG, SH, k, min_density): |
|
||||||
for k_nodes in biconnected_components(Gc): |
|
||||||
Gk = nx.k_core(SG.subgraph(k_nodes), k) |
|
||||||
if len(Gk) <= k: |
|
||||||
continue |
|
||||||
k_components[k].append(set(Gk)) |
|
||||||
return k_components |
|
||||||
|
|
||||||
|
|
||||||
def _cliques_heuristic(G, H, k, min_density): |
|
||||||
h_cnumber = nx.core_number(H) |
|
||||||
for i, c_value in enumerate(sorted(set(h_cnumber.values()), reverse=True)): |
|
||||||
cands = {n for n, c in h_cnumber.items() if c == c_value} |
|
||||||
# Skip checking for overlap for the highest core value |
|
||||||
if i == 0: |
|
||||||
overlap = False |
|
||||||
else: |
|
||||||
overlap = set.intersection( |
|
||||||
*[{x for x in H[n] if x not in cands} for n in cands] |
|
||||||
) |
|
||||||
if overlap and len(overlap) < k: |
|
||||||
SH = H.subgraph(cands | overlap) |
|
||||||
else: |
|
||||||
SH = H.subgraph(cands) |
|
||||||
sh_cnumber = nx.core_number(SH) |
|
||||||
SG = nx.k_core(G.subgraph(SH), k) |
|
||||||
while not (_same(sh_cnumber) and nx.density(SH) >= min_density): |
|
||||||
# This subgraph must be writable => .copy() |
|
||||||
SH = H.subgraph(SG).copy() |
|
||||||
if len(SH) <= k: |
|
||||||
break |
|
||||||
sh_cnumber = nx.core_number(SH) |
|
||||||
sh_deg = dict(SH.degree()) |
|
||||||
min_deg = min(sh_deg.values()) |
|
||||||
SH.remove_nodes_from(n for n, d in sh_deg.items() if d == min_deg) |
|
||||||
SG = nx.k_core(G.subgraph(SH), k) |
|
||||||
else: |
|
||||||
yield SG |
|
||||||
|
|
||||||
|
|
||||||
def _same(measure, tol=0): |
|
||||||
vals = set(measure.values()) |
|
||||||
if (max(vals) - min(vals)) <= tol: |
|
||||||
return True |
|
||||||
return False |
|
||||||
|
|
||||||
|
|
||||||
class _AntiGraph(nx.Graph): |
|
||||||
""" |
|
||||||
Class for complement graphs. |
|
||||||
|
|
||||||
The main goal is to be able to work with big and dense graphs with |
|
||||||
a low memory foodprint. |
|
||||||
|
|
||||||
In this class you add the edges that *do not exist* in the dense graph, |
|
||||||
the report methods of the class return the neighbors, the edges and |
|
||||||
the degree as if it was the dense graph. Thus it's possible to use |
|
||||||
an instance of this class with some of NetworkX functions. In this |
|
||||||
case we only use k-core, connected_components, and biconnected_components. |
|
||||||
""" |
|
||||||
|
|
||||||
all_edge_dict = {"weight": 1} |
|
||||||
|
|
||||||
def single_edge_dict(self): |
|
||||||
return self.all_edge_dict |
|
||||||
|
|
||||||
edge_attr_dict_factory = single_edge_dict |
|
||||||
|
|
||||||
def __getitem__(self, n): |
|
||||||
"""Returns a dict of neighbors of node n in the dense graph. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
n : node |
|
||||||
A node in the graph. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
adj_dict : dictionary |
|
||||||
The adjacency dictionary for nodes connected to n. |
|
||||||
|
|
||||||
""" |
|
||||||
all_edge_dict = self.all_edge_dict |
|
||||||
return { |
|
||||||
node: all_edge_dict for node in set(self._adj) - set(self._adj[n]) - {n} |
|
||||||
} |
|
||||||
|
|
||||||
def neighbors(self, n): |
|
||||||
"""Returns an iterator over all neighbors of node n in the |
|
||||||
dense graph. |
|
||||||
""" |
|
||||||
try: |
|
||||||
return iter(set(self._adj) - set(self._adj[n]) - {n}) |
|
||||||
except KeyError as e: |
|
||||||
raise NetworkXError(f"The node {n} is not in the graph.") from e |
|
||||||
|
|
||||||
class AntiAtlasView(Mapping): |
|
||||||
"""An adjacency inner dict for AntiGraph""" |
|
||||||
|
|
||||||
def __init__(self, graph, node): |
|
||||||
self._graph = graph |
|
||||||
self._atlas = graph._adj[node] |
|
||||||
self._node = node |
|
||||||
|
|
||||||
def __len__(self): |
|
||||||
return len(self._graph) - len(self._atlas) - 1 |
|
||||||
|
|
||||||
def __iter__(self): |
|
||||||
return (n for n in self._graph if n not in self._atlas and n != self._node) |
|
||||||
|
|
||||||
def __getitem__(self, nbr): |
|
||||||
nbrs = set(self._graph._adj) - set(self._atlas) - {self._node} |
|
||||||
if nbr in nbrs: |
|
||||||
return self._graph.all_edge_dict |
|
||||||
raise KeyError(nbr) |
|
||||||
|
|
||||||
class AntiAdjacencyView(AntiAtlasView): |
|
||||||
"""An adjacency outer dict for AntiGraph""" |
|
||||||
|
|
||||||
def __init__(self, graph): |
|
||||||
self._graph = graph |
|
||||||
self._atlas = graph._adj |
|
||||||
|
|
||||||
def __len__(self): |
|
||||||
return len(self._atlas) |
|
||||||
|
|
||||||
def __iter__(self): |
|
||||||
return iter(self._graph) |
|
||||||
|
|
||||||
def __getitem__(self, node): |
|
||||||
if node not in self._graph: |
|
||||||
raise KeyError(node) |
|
||||||
return self._graph.AntiAtlasView(self._graph, node) |
|
||||||
|
|
||||||
@property |
|
||||||
def adj(self): |
|
||||||
return self.AntiAdjacencyView(self) |
|
||||||
|
|
||||||
def subgraph(self, nodes): |
|
||||||
"""This subgraph method returns a full AntiGraph. Not a View""" |
|
||||||
nodes = set(nodes) |
|
||||||
G = _AntiGraph() |
|
||||||
G.add_nodes_from(nodes) |
|
||||||
for n in G: |
|
||||||
Gnbrs = G.adjlist_inner_dict_factory() |
|
||||||
G._adj[n] = Gnbrs |
|
||||||
for nbr, d in self._adj[n].items(): |
|
||||||
if nbr in G._adj: |
|
||||||
Gnbrs[nbr] = d |
|
||||||
G._adj[nbr][n] = d |
|
||||||
G.graph = self.graph |
|
||||||
return G |
|
||||||
|
|
||||||
class AntiDegreeView(nx.reportviews.DegreeView): |
|
||||||
def __iter__(self): |
|
||||||
all_nodes = set(self._succ) |
|
||||||
for n in self._nodes: |
|
||||||
nbrs = all_nodes - set(self._succ[n]) - {n} |
|
||||||
yield (n, len(nbrs)) |
|
||||||
|
|
||||||
def __getitem__(self, n): |
|
||||||
nbrs = set(self._succ) - set(self._succ[n]) - {n} |
|
||||||
# AntiGraph is a ThinGraph so all edges have weight 1 |
|
||||||
return len(nbrs) + (n in nbrs) |
|
||||||
|
|
||||||
@property |
|
||||||
def degree(self): |
|
||||||
"""Returns an iterator for (node, degree) and degree for single node. |
|
||||||
|
|
||||||
The node degree is the number of edges adjacent to the node. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
nbunch : iterable container, optional (default=all nodes) |
|
||||||
A container of nodes. The container will be iterated |
|
||||||
through once. |
|
||||||
|
|
||||||
weight : string or None, optional (default=None) |
|
||||||
The edge attribute that holds the numerical value used |
|
||||||
as a weight. If None, then each edge has weight 1. |
|
||||||
The degree is the sum of the edge weights adjacent to the node. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
deg: |
|
||||||
Degree of the node, if a single node is passed as argument. |
|
||||||
nd_iter : an iterator |
|
||||||
The iterator returns two-tuples of (node, degree). |
|
||||||
|
|
||||||
See Also |
|
||||||
-------- |
|
||||||
degree |
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
>>> G = nx.path_graph(4) |
|
||||||
>>> G.degree(0) # node 0 with degree 1 |
|
||||||
1 |
|
||||||
>>> list(G.degree([0, 1])) |
|
||||||
[(0, 1), (1, 2)] |
|
||||||
|
|
||||||
""" |
|
||||||
return self.AntiDegreeView(self) |
|
||||||
|
|
||||||
def adjacency(self): |
|
||||||
"""Returns an iterator of (node, adjacency set) tuples for all nodes |
|
||||||
in the dense graph. |
|
||||||
|
|
||||||
This is the fastest way to look at every edge. |
|
||||||
For directed graphs, only outgoing adjacencies are included. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
adj_iter : iterator |
|
||||||
An iterator of (node, adjacency set) for all nodes in |
|
||||||
the graph. |
|
||||||
|
|
||||||
""" |
|
||||||
for n in self._adj: |
|
||||||
yield (n, set(self._adj) - set(self._adj[n]) - {n}) |
|
@ -1,42 +0,0 @@ |
|||||||
""" |
|
||||||
************** |
|
||||||
Graph Matching |
|
||||||
************** |
|
||||||
|
|
||||||
Given a graph G = (V,E), a matching M in G is a set of pairwise non-adjacent |
|
||||||
edges; that is, no two edges share a common vertex. |
|
||||||
|
|
||||||
`Wikipedia: Matching <https://en.wikipedia.org/wiki/Matching_(graph_theory)>`_ |
|
||||||
""" |
|
||||||
import networkx as nx |
|
||||||
|
|
||||||
__all__ = ["min_maximal_matching"] |
|
||||||
|
|
||||||
|
|
||||||
def min_maximal_matching(G): |
|
||||||
r"""Returns the minimum maximal matching of G. That is, out of all maximal |
|
||||||
matchings of the graph G, the smallest is returned. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
Undirected graph |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
min_maximal_matching : set |
|
||||||
Returns a set of edges such that no two edges share a common endpoint |
|
||||||
and every edge not in the set shares some common endpoint in the set. |
|
||||||
Cardinality will be 2*OPT in the worst case. |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
The algorithm computes an approximate solution fo the minimum maximal |
|
||||||
cardinality matching problem. The solution is no more than 2 * OPT in size. |
|
||||||
Runtime is $O(|E|)$. |
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] Vazirani, Vijay Approximation Algorithms (2001) |
|
||||||
""" |
|
||||||
return nx.maximal_matching(G) |
|
@ -1,42 +0,0 @@ |
|||||||
""" |
|
||||||
Ramsey numbers. |
|
||||||
""" |
|
||||||
import networkx as nx |
|
||||||
from ...utils import arbitrary_element |
|
||||||
|
|
||||||
__all__ = ["ramsey_R2"] |
|
||||||
|
|
||||||
|
|
||||||
def ramsey_R2(G): |
|
||||||
r"""Compute the largest clique and largest independent set in `G`. |
|
||||||
|
|
||||||
This can be used to estimate bounds for the 2-color |
|
||||||
Ramsey number `R(2;s,t)` for `G`. |
|
||||||
|
|
||||||
This is a recursive implementation which could run into trouble |
|
||||||
for large recursions. Note that self-loop edges are ignored. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
Undirected graph |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
max_pair : (set, set) tuple |
|
||||||
Maximum clique, Maximum independent set. |
|
||||||
""" |
|
||||||
if not G: |
|
||||||
return set(), set() |
|
||||||
|
|
||||||
node = arbitrary_element(G) |
|
||||||
nbrs = (nbr for nbr in nx.all_neighbors(G, node) if nbr != node) |
|
||||||
nnbrs = nx.non_neighbors(G, node) |
|
||||||
c_1, i_1 = ramsey_R2(G.subgraph(nbrs).copy()) |
|
||||||
c_2, i_2 = ramsey_R2(G.subgraph(nnbrs).copy()) |
|
||||||
|
|
||||||
c_1.add(node) |
|
||||||
i_2.add(node) |
|
||||||
# Choose the larger of the two cliques and the larger of the two |
|
||||||
# independent sets, according to cardinality. |
|
||||||
return max(c_1, c_2, key=len), max(i_1, i_2, key=len) |
|
@ -1,104 +0,0 @@ |
|||||||
from itertools import chain |
|
||||||
|
|
||||||
from networkx.utils import pairwise, not_implemented_for |
|
||||||
import networkx as nx |
|
||||||
|
|
||||||
__all__ = ["metric_closure", "steiner_tree"] |
|
||||||
|
|
||||||
|
|
||||||
@not_implemented_for("directed") |
|
||||||
def metric_closure(G, weight="weight"): |
|
||||||
""" Return the metric closure of a graph. |
|
||||||
|
|
||||||
The metric closure of a graph *G* is the complete graph in which each edge |
|
||||||
is weighted by the shortest path distance between the nodes in *G* . |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
NetworkX graph |
|
||||||
Metric closure of the graph `G`. |
|
||||||
|
|
||||||
""" |
|
||||||
M = nx.Graph() |
|
||||||
|
|
||||||
Gnodes = set(G) |
|
||||||
|
|
||||||
# check for connected graph while processing first node |
|
||||||
all_paths_iter = nx.all_pairs_dijkstra(G, weight=weight) |
|
||||||
u, (distance, path) = next(all_paths_iter) |
|
||||||
if Gnodes - set(distance): |
|
||||||
msg = "G is not a connected graph. metric_closure is not defined." |
|
||||||
raise nx.NetworkXError(msg) |
|
||||||
Gnodes.remove(u) |
|
||||||
for v in Gnodes: |
|
||||||
M.add_edge(u, v, distance=distance[v], path=path[v]) |
|
||||||
|
|
||||||
# first node done -- now process the rest |
|
||||||
for u, (distance, path) in all_paths_iter: |
|
||||||
Gnodes.remove(u) |
|
||||||
for v in Gnodes: |
|
||||||
M.add_edge(u, v, distance=distance[v], path=path[v]) |
|
||||||
|
|
||||||
return M |
|
||||||
|
|
||||||
|
|
||||||
@not_implemented_for("directed") |
|
||||||
def steiner_tree(G, terminal_nodes, weight="weight"): |
|
||||||
""" Return an approximation to the minimum Steiner tree of a graph. |
|
||||||
|
|
||||||
The minimum Steiner tree of `G` w.r.t a set of `terminal_nodes` |
|
||||||
is a tree within `G` that spans those nodes and has minimum size |
|
||||||
(sum of edge weights) among all such trees. |
|
||||||
|
|
||||||
The minimum Steiner tree can be approximated by computing the minimum |
|
||||||
spanning tree of the subgraph of the metric closure of *G* induced by the |
|
||||||
terminal nodes, where the metric closure of *G* is the complete graph in |
|
||||||
which each edge is weighted by the shortest path distance between the |
|
||||||
nodes in *G* . |
|
||||||
This algorithm produces a tree whose weight is within a (2 - (2 / t)) |
|
||||||
factor of the weight of the optimal Steiner tree where *t* is number of |
|
||||||
terminal nodes. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
|
|
||||||
terminal_nodes : list |
|
||||||
A list of terminal nodes for which minimum steiner tree is |
|
||||||
to be found. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
NetworkX graph |
|
||||||
Approximation to the minimum steiner tree of `G` induced by |
|
||||||
`terminal_nodes` . |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
For multigraphs, the edge between two nodes with minimum weight is the |
|
||||||
edge put into the Steiner tree. |
|
||||||
|
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] Steiner_tree_problem on Wikipedia. |
|
||||||
https://en.wikipedia.org/wiki/Steiner_tree_problem |
|
||||||
""" |
|
||||||
# H is the subgraph induced by terminal_nodes in the metric closure M of G. |
|
||||||
M = metric_closure(G, weight=weight) |
|
||||||
H = M.subgraph(terminal_nodes) |
|
||||||
# Use the 'distance' attribute of each edge provided by M. |
|
||||||
mst_edges = nx.minimum_spanning_edges(H, weight="distance", data=True) |
|
||||||
# Create an iterator over each edge in each shortest path; repeats are okay |
|
||||||
edges = chain.from_iterable(pairwise(d["path"]) for u, v, d in mst_edges) |
|
||||||
# For multigraph we should add the minimal weight edge keys |
|
||||||
if G.is_multigraph(): |
|
||||||
edges = ( |
|
||||||
(u, v, min(G[u][v], key=lambda k: G[u][v][k][weight])) for u, v in edges |
|
||||||
) |
|
||||||
T = G.edge_subgraph(edges) |
|
||||||
return T |
|
@ -1,43 +0,0 @@ |
|||||||
import networkx as nx |
|
||||||
from networkx.algorithms.approximation import average_clustering |
|
||||||
|
|
||||||
# This approximation has to be be exact in regular graphs |
|
||||||
# with no triangles or with all possible triangles. |
|
||||||
|
|
||||||
|
|
||||||
def test_petersen(): |
|
||||||
# Actual coefficient is 0 |
|
||||||
G = nx.petersen_graph() |
|
||||||
assert average_clustering(G, trials=int(len(G) / 2)) == nx.average_clustering(G) |
|
||||||
|
|
||||||
|
|
||||||
def test_petersen_seed(): |
|
||||||
# Actual coefficient is 0 |
|
||||||
G = nx.petersen_graph() |
|
||||||
assert average_clustering( |
|
||||||
G, trials=int(len(G) / 2), seed=1 |
|
||||||
) == nx.average_clustering(G) |
|
||||||
|
|
||||||
|
|
||||||
def test_tetrahedral(): |
|
||||||
# Actual coefficient is 1 |
|
||||||
G = nx.tetrahedral_graph() |
|
||||||
assert average_clustering(G, trials=int(len(G) / 2)) == nx.average_clustering(G) |
|
||||||
|
|
||||||
|
|
||||||
def test_dodecahedral(): |
|
||||||
# Actual coefficient is 0 |
|
||||||
G = nx.dodecahedral_graph() |
|
||||||
assert average_clustering(G, trials=int(len(G) / 2)) == nx.average_clustering(G) |
|
||||||
|
|
||||||
|
|
||||||
def test_empty(): |
|
||||||
G = nx.empty_graph(5) |
|
||||||
assert average_clustering(G, trials=int(len(G) / 2)) == 0 |
|
||||||
|
|
||||||
|
|
||||||
def test_complete(): |
|
||||||
G = nx.complete_graph(5) |
|
||||||
assert average_clustering(G, trials=int(len(G) / 2)) == 1 |
|
||||||
G = nx.complete_graph(7) |
|
||||||
assert average_clustering(G, trials=int(len(G) / 2)) == 1 |
|
@ -1,107 +0,0 @@ |
|||||||
"""Unit tests for the :mod:`networkx.algorithms.approximation.clique` |
|
||||||
module. |
|
||||||
|
|
||||||
""" |
|
||||||
|
|
||||||
|
|
||||||
import networkx as nx |
|
||||||
from networkx.algorithms.approximation import max_clique |
|
||||||
from networkx.algorithms.approximation import clique_removal |
|
||||||
from networkx.algorithms.approximation import large_clique_size |
|
||||||
|
|
||||||
|
|
||||||
def is_independent_set(G, nodes): |
|
||||||
"""Returns True if and only if `nodes` is a clique in `G`. |
|
||||||
|
|
||||||
`G` is a NetworkX graph. `nodes` is an iterable of nodes in |
|
||||||
`G`. |
|
||||||
|
|
||||||
""" |
|
||||||
return G.subgraph(nodes).number_of_edges() == 0 |
|
||||||
|
|
||||||
|
|
||||||
def is_clique(G, nodes): |
|
||||||
"""Returns True if and only if `nodes` is an independent set |
|
||||||
in `G`. |
|
||||||
|
|
||||||
`G` is an undirected simple graph. `nodes` is an iterable of |
|
||||||
nodes in `G`. |
|
||||||
|
|
||||||
""" |
|
||||||
H = G.subgraph(nodes) |
|
||||||
n = len(H) |
|
||||||
return H.number_of_edges() == n * (n - 1) // 2 |
|
||||||
|
|
||||||
|
|
||||||
class TestCliqueRemoval: |
|
||||||
"""Unit tests for the |
|
||||||
:func:`~networkx.algorithms.approximation.clique_removal` function. |
|
||||||
|
|
||||||
""" |
|
||||||
|
|
||||||
def test_trivial_graph(self): |
|
||||||
G = nx.trivial_graph() |
|
||||||
independent_set, cliques = clique_removal(G) |
|
||||||
assert is_independent_set(G, independent_set) |
|
||||||
assert all(is_clique(G, clique) for clique in cliques) |
|
||||||
# In fact, we should only have 1-cliques, that is, singleton nodes. |
|
||||||
assert all(len(clique) == 1 for clique in cliques) |
|
||||||
|
|
||||||
def test_complete_graph(self): |
|
||||||
G = nx.complete_graph(10) |
|
||||||
independent_set, cliques = clique_removal(G) |
|
||||||
assert is_independent_set(G, independent_set) |
|
||||||
assert all(is_clique(G, clique) for clique in cliques) |
|
||||||
|
|
||||||
def test_barbell_graph(self): |
|
||||||
G = nx.barbell_graph(10, 5) |
|
||||||
independent_set, cliques = clique_removal(G) |
|
||||||
assert is_independent_set(G, independent_set) |
|
||||||
assert all(is_clique(G, clique) for clique in cliques) |
|
||||||
|
|
||||||
|
|
||||||
class TestMaxClique: |
|
||||||
"""Unit tests for the :func:`networkx.algorithms.approximation.max_clique` |
|
||||||
function. |
|
||||||
|
|
||||||
""" |
|
||||||
|
|
||||||
def test_null_graph(self): |
|
||||||
G = nx.null_graph() |
|
||||||
assert len(max_clique(G)) == 0 |
|
||||||
|
|
||||||
def test_complete_graph(self): |
|
||||||
graph = nx.complete_graph(30) |
|
||||||
# this should return the entire graph |
|
||||||
mc = max_clique(graph) |
|
||||||
assert 30 == len(mc) |
|
||||||
|
|
||||||
def test_maximal_by_cardinality(self): |
|
||||||
"""Tests that the maximal clique is computed according to maximum |
|
||||||
cardinality of the sets. |
|
||||||
|
|
||||||
For more information, see pull request #1531. |
|
||||||
|
|
||||||
""" |
|
||||||
G = nx.complete_graph(5) |
|
||||||
G.add_edge(4, 5) |
|
||||||
clique = max_clique(G) |
|
||||||
assert len(clique) > 1 |
|
||||||
|
|
||||||
G = nx.lollipop_graph(30, 2) |
|
||||||
clique = max_clique(G) |
|
||||||
assert len(clique) > 2 |
|
||||||
|
|
||||||
|
|
||||||
def test_large_clique_size(): |
|
||||||
G = nx.complete_graph(9) |
|
||||||
nx.add_cycle(G, [9, 10, 11]) |
|
||||||
G.add_edge(8, 9) |
|
||||||
G.add_edge(1, 12) |
|
||||||
G.add_node(13) |
|
||||||
|
|
||||||
assert large_clique_size(G) == 9 |
|
||||||
G.remove_node(5) |
|
||||||
assert large_clique_size(G) == 8 |
|
||||||
G.remove_edge(2, 3) |
|
||||||
assert large_clique_size(G) == 7 |
|
@ -1,199 +0,0 @@ |
|||||||
import pytest |
|
||||||
|
|
||||||
import networkx as nx |
|
||||||
from networkx.algorithms import approximation as approx |
|
||||||
|
|
||||||
|
|
||||||
def test_global_node_connectivity(): |
|
||||||
# Figure 1 chapter on Connectivity |
|
||||||
G = nx.Graph() |
|
||||||
G.add_edges_from( |
|
||||||
[ |
|
||||||
(1, 2), |
|
||||||
(1, 3), |
|
||||||
(1, 4), |
|
||||||
(1, 5), |
|
||||||
(2, 3), |
|
||||||
(2, 6), |
|
||||||
(3, 4), |
|
||||||
(3, 6), |
|
||||||
(4, 6), |
|
||||||
(4, 7), |
|
||||||
(5, 7), |
|
||||||
(6, 8), |
|
||||||
(6, 9), |
|
||||||
(7, 8), |
|
||||||
(7, 10), |
|
||||||
(8, 11), |
|
||||||
(9, 10), |
|
||||||
(9, 11), |
|
||||||
(10, 11), |
|
||||||
] |
|
||||||
) |
|
||||||
assert 2 == approx.local_node_connectivity(G, 1, 11) |
|
||||||
assert 2 == approx.node_connectivity(G) |
|
||||||
assert 2 == approx.node_connectivity(G, 1, 11) |
|
||||||
|
|
||||||
|
|
||||||
def test_white_harary1(): |
|
||||||
# Figure 1b white and harary (2001) |
|
||||||
# A graph with high adhesion (edge connectivity) and low cohesion |
|
||||||
# (node connectivity) |
|
||||||
G = nx.disjoint_union(nx.complete_graph(4), nx.complete_graph(4)) |
|
||||||
G.remove_node(7) |
|
||||||
for i in range(4, 7): |
|
||||||
G.add_edge(0, i) |
|
||||||
G = nx.disjoint_union(G, nx.complete_graph(4)) |
|
||||||
G.remove_node(G.order() - 1) |
|
||||||
for i in range(7, 10): |
|
||||||
G.add_edge(0, i) |
|
||||||
assert 1 == approx.node_connectivity(G) |
|
||||||
|
|
||||||
|
|
||||||
def test_complete_graphs(): |
|
||||||
for n in range(5, 25, 5): |
|
||||||
G = nx.complete_graph(n) |
|
||||||
assert n - 1 == approx.node_connectivity(G) |
|
||||||
assert n - 1 == approx.node_connectivity(G, 0, 3) |
|
||||||
|
|
||||||
|
|
||||||
def test_empty_graphs(): |
|
||||||
for k in range(5, 25, 5): |
|
||||||
G = nx.empty_graph(k) |
|
||||||
assert 0 == approx.node_connectivity(G) |
|
||||||
assert 0 == approx.node_connectivity(G, 0, 3) |
|
||||||
|
|
||||||
|
|
||||||
def test_petersen(): |
|
||||||
G = nx.petersen_graph() |
|
||||||
assert 3 == approx.node_connectivity(G) |
|
||||||
assert 3 == approx.node_connectivity(G, 0, 5) |
|
||||||
|
|
||||||
|
|
||||||
# Approximation fails with tutte graph |
|
||||||
# def test_tutte(): |
|
||||||
# G = nx.tutte_graph() |
|
||||||
# assert_equal(3, approx.node_connectivity(G)) |
|
||||||
|
|
||||||
|
|
||||||
def test_dodecahedral(): |
|
||||||
G = nx.dodecahedral_graph() |
|
||||||
assert 3 == approx.node_connectivity(G) |
|
||||||
assert 3 == approx.node_connectivity(G, 0, 5) |
|
||||||
|
|
||||||
|
|
||||||
def test_octahedral(): |
|
||||||
G = nx.octahedral_graph() |
|
||||||
assert 4 == approx.node_connectivity(G) |
|
||||||
assert 4 == approx.node_connectivity(G, 0, 5) |
|
||||||
|
|
||||||
|
|
||||||
# Approximation can fail with icosahedral graph depending |
|
||||||
# on iteration order. |
|
||||||
# def test_icosahedral(): |
|
||||||
# G=nx.icosahedral_graph() |
|
||||||
# assert_equal(5, approx.node_connectivity(G)) |
|
||||||
# assert_equal(5, approx.node_connectivity(G, 0, 5)) |
|
||||||
|
|
||||||
|
|
||||||
def test_only_source(): |
|
||||||
G = nx.complete_graph(5) |
|
||||||
pytest.raises(nx.NetworkXError, approx.node_connectivity, G, s=0) |
|
||||||
|
|
||||||
|
|
||||||
def test_only_target(): |
|
||||||
G = nx.complete_graph(5) |
|
||||||
pytest.raises(nx.NetworkXError, approx.node_connectivity, G, t=0) |
|
||||||
|
|
||||||
|
|
||||||
def test_missing_source(): |
|
||||||
G = nx.path_graph(4) |
|
||||||
pytest.raises(nx.NetworkXError, approx.node_connectivity, G, 10, 1) |
|
||||||
|
|
||||||
|
|
||||||
def test_missing_target(): |
|
||||||
G = nx.path_graph(4) |
|
||||||
pytest.raises(nx.NetworkXError, approx.node_connectivity, G, 1, 10) |
|
||||||
|
|
||||||
|
|
||||||
def test_source_equals_target(): |
|
||||||
G = nx.complete_graph(5) |
|
||||||
pytest.raises(nx.NetworkXError, approx.local_node_connectivity, G, 0, 0) |
|
||||||
|
|
||||||
|
|
||||||
def test_directed_node_connectivity(): |
|
||||||
G = nx.cycle_graph(10, create_using=nx.DiGraph()) # only one direction |
|
||||||
D = nx.cycle_graph(10).to_directed() # 2 reciprocal edges |
|
||||||
assert 1 == approx.node_connectivity(G) |
|
||||||
assert 1 == approx.node_connectivity(G, 1, 4) |
|
||||||
assert 2 == approx.node_connectivity(D) |
|
||||||
assert 2 == approx.node_connectivity(D, 1, 4) |
|
||||||
|
|
||||||
|
|
||||||
class TestAllPairsNodeConnectivityApprox: |
|
||||||
@classmethod |
|
||||||
def setup_class(cls): |
|
||||||
cls.path = nx.path_graph(7) |
|
||||||
cls.directed_path = nx.path_graph(7, create_using=nx.DiGraph()) |
|
||||||
cls.cycle = nx.cycle_graph(7) |
|
||||||
cls.directed_cycle = nx.cycle_graph(7, create_using=nx.DiGraph()) |
|
||||||
cls.gnp = nx.gnp_random_graph(30, 0.1) |
|
||||||
cls.directed_gnp = nx.gnp_random_graph(30, 0.1, directed=True) |
|
||||||
cls.K20 = nx.complete_graph(20) |
|
||||||
cls.K10 = nx.complete_graph(10) |
|
||||||
cls.K5 = nx.complete_graph(5) |
|
||||||
cls.G_list = [ |
|
||||||
cls.path, |
|
||||||
cls.directed_path, |
|
||||||
cls.cycle, |
|
||||||
cls.directed_cycle, |
|
||||||
cls.gnp, |
|
||||||
cls.directed_gnp, |
|
||||||
cls.K10, |
|
||||||
cls.K5, |
|
||||||
cls.K20, |
|
||||||
] |
|
||||||
|
|
||||||
def test_cycles(self): |
|
||||||
K_undir = approx.all_pairs_node_connectivity(self.cycle) |
|
||||||
for source in K_undir: |
|
||||||
for target, k in K_undir[source].items(): |
|
||||||
assert k == 2 |
|
||||||
K_dir = approx.all_pairs_node_connectivity(self.directed_cycle) |
|
||||||
for source in K_dir: |
|
||||||
for target, k in K_dir[source].items(): |
|
||||||
assert k == 1 |
|
||||||
|
|
||||||
def test_complete(self): |
|
||||||
for G in [self.K10, self.K5, self.K20]: |
|
||||||
K = approx.all_pairs_node_connectivity(G) |
|
||||||
for source in K: |
|
||||||
for target, k in K[source].items(): |
|
||||||
assert k == len(G) - 1 |
|
||||||
|
|
||||||
def test_paths(self): |
|
||||||
K_undir = approx.all_pairs_node_connectivity(self.path) |
|
||||||
for source in K_undir: |
|
||||||
for target, k in K_undir[source].items(): |
|
||||||
assert k == 1 |
|
||||||
K_dir = approx.all_pairs_node_connectivity(self.directed_path) |
|
||||||
for source in K_dir: |
|
||||||
for target, k in K_dir[source].items(): |
|
||||||
if source < target: |
|
||||||
assert k == 1 |
|
||||||
else: |
|
||||||
assert k == 0 |
|
||||||
|
|
||||||
def test_cutoff(self): |
|
||||||
for G in [self.K10, self.K5, self.K20]: |
|
||||||
for mp in [2, 3, 4]: |
|
||||||
paths = approx.all_pairs_node_connectivity(G, cutoff=mp) |
|
||||||
for source in paths: |
|
||||||
for target, K in paths[source].items(): |
|
||||||
assert K == mp |
|
||||||
|
|
||||||
def test_all_pairs_connectivity_nbunch(self): |
|
||||||
G = nx.complete_graph(5) |
|
||||||
nbunch = [0, 2, 3] |
|
||||||
C = approx.all_pairs_node_connectivity(G, nbunch=nbunch) |
|
||||||
assert len(C) == len(nbunch) |
|
@ -1,65 +0,0 @@ |
|||||||
import networkx as nx |
|
||||||
from networkx.algorithms.approximation import min_weighted_dominating_set |
|
||||||
from networkx.algorithms.approximation import min_edge_dominating_set |
|
||||||
|
|
||||||
|
|
||||||
class TestMinWeightDominatingSet: |
|
||||||
def test_min_weighted_dominating_set(self): |
|
||||||
graph = nx.Graph() |
|
||||||
graph.add_edge(1, 2) |
|
||||||
graph.add_edge(1, 5) |
|
||||||
graph.add_edge(2, 3) |
|
||||||
graph.add_edge(2, 5) |
|
||||||
graph.add_edge(3, 4) |
|
||||||
graph.add_edge(3, 6) |
|
||||||
graph.add_edge(5, 6) |
|
||||||
|
|
||||||
vertices = {1, 2, 3, 4, 5, 6} |
|
||||||
# due to ties, this might be hard to test tight bounds |
|
||||||
dom_set = min_weighted_dominating_set(graph) |
|
||||||
for vertex in vertices - dom_set: |
|
||||||
neighbors = set(graph.neighbors(vertex)) |
|
||||||
assert len(neighbors & dom_set) > 0, "Non dominating set found!" |
|
||||||
|
|
||||||
def test_star_graph(self): |
|
||||||
"""Tests that an approximate dominating set for the star graph, |
|
||||||
even when the center node does not have the smallest integer |
|
||||||
label, gives just the center node. |
|
||||||
|
|
||||||
For more information, see #1527. |
|
||||||
|
|
||||||
""" |
|
||||||
# Create a star graph in which the center node has the highest |
|
||||||
# label instead of the lowest. |
|
||||||
G = nx.star_graph(10) |
|
||||||
G = nx.relabel_nodes(G, {0: 9, 9: 0}) |
|
||||||
assert min_weighted_dominating_set(G) == {9} |
|
||||||
|
|
||||||
def test_min_edge_dominating_set(self): |
|
||||||
graph = nx.path_graph(5) |
|
||||||
dom_set = min_edge_dominating_set(graph) |
|
||||||
|
|
||||||
# this is a crappy way to test, but good enough for now. |
|
||||||
for edge in graph.edges(): |
|
||||||
if edge in dom_set: |
|
||||||
continue |
|
||||||
else: |
|
||||||
u, v = edge |
|
||||||
found = False |
|
||||||
for dom_edge in dom_set: |
|
||||||
found |= u == dom_edge[0] or u == dom_edge[1] |
|
||||||
assert found, "Non adjacent edge found!" |
|
||||||
|
|
||||||
graph = nx.complete_graph(10) |
|
||||||
dom_set = min_edge_dominating_set(graph) |
|
||||||
|
|
||||||
# this is a crappy way to test, but good enough for now. |
|
||||||
for edge in graph.edges(): |
|
||||||
if edge in dom_set: |
|
||||||
continue |
|
||||||
else: |
|
||||||
u, v = edge |
|
||||||
found = False |
|
||||||
for dom_edge in dom_set: |
|
||||||
found |= u == dom_edge[0] or u == dom_edge[1] |
|
||||||
assert found, "Non adjacent edge found!" |
|
@ -1,8 +0,0 @@ |
|||||||
import networkx as nx |
|
||||||
import networkx.algorithms.approximation as a |
|
||||||
|
|
||||||
|
|
||||||
def test_independent_set(): |
|
||||||
# smoke test |
|
||||||
G = nx.Graph() |
|
||||||
assert len(a.maximum_independent_set(G)) == 0 |
|
@ -1,300 +0,0 @@ |
|||||||
# Test for approximation to k-components algorithm |
|
||||||
import pytest |
|
||||||
import networkx as nx |
|
||||||
from networkx.algorithms.approximation import k_components |
|
||||||
from networkx.algorithms.approximation.kcomponents import _AntiGraph, _same |
|
||||||
|
|
||||||
|
|
||||||
def build_k_number_dict(k_components): |
|
||||||
k_num = {} |
|
||||||
for k, comps in sorted(k_components.items()): |
|
||||||
for comp in comps: |
|
||||||
for node in comp: |
|
||||||
k_num[node] = k |
|
||||||
return k_num |
|
||||||
|
|
||||||
|
|
||||||
## |
|
||||||
# Some nice synthetic graphs |
|
||||||
## |
|
||||||
|
|
||||||
|
|
||||||
def graph_example_1(): |
|
||||||
G = nx.convert_node_labels_to_integers( |
|
||||||
nx.grid_graph([5, 5]), label_attribute="labels" |
|
||||||
) |
|
||||||
rlabels = nx.get_node_attributes(G, "labels") |
|
||||||
labels = {v: k for k, v in rlabels.items()} |
|
||||||
|
|
||||||
for nodes in [ |
|
||||||
(labels[(0, 0)], labels[(1, 0)]), |
|
||||||
(labels[(0, 4)], labels[(1, 4)]), |
|
||||||
(labels[(3, 0)], labels[(4, 0)]), |
|
||||||
(labels[(3, 4)], labels[(4, 4)]), |
|
||||||
]: |
|
||||||
new_node = G.order() + 1 |
|
||||||
# Petersen graph is triconnected |
|
||||||
P = nx.petersen_graph() |
|
||||||
G = nx.disjoint_union(G, P) |
|
||||||
# Add two edges between the grid and P |
|
||||||
G.add_edge(new_node + 1, nodes[0]) |
|
||||||
G.add_edge(new_node, nodes[1]) |
|
||||||
# K5 is 4-connected |
|
||||||
K = nx.complete_graph(5) |
|
||||||
G = nx.disjoint_union(G, K) |
|
||||||
# Add three edges between P and K5 |
|
||||||
G.add_edge(new_node + 2, new_node + 11) |
|
||||||
G.add_edge(new_node + 3, new_node + 12) |
|
||||||
G.add_edge(new_node + 4, new_node + 13) |
|
||||||
# Add another K5 sharing a node |
|
||||||
G = nx.disjoint_union(G, K) |
|
||||||
nbrs = G[new_node + 10] |
|
||||||
G.remove_node(new_node + 10) |
|
||||||
for nbr in nbrs: |
|
||||||
G.add_edge(new_node + 17, nbr) |
|
||||||
G.add_edge(new_node + 16, new_node + 5) |
|
||||||
return G |
|
||||||
|
|
||||||
|
|
||||||
def torrents_and_ferraro_graph(): |
|
||||||
G = nx.convert_node_labels_to_integers( |
|
||||||
nx.grid_graph([5, 5]), label_attribute="labels" |
|
||||||
) |
|
||||||
rlabels = nx.get_node_attributes(G, "labels") |
|
||||||
labels = {v: k for k, v in rlabels.items()} |
|
||||||
|
|
||||||
for nodes in [(labels[(0, 4)], labels[(1, 4)]), (labels[(3, 4)], labels[(4, 4)])]: |
|
||||||
new_node = G.order() + 1 |
|
||||||
# Petersen graph is triconnected |
|
||||||
P = nx.petersen_graph() |
|
||||||
G = nx.disjoint_union(G, P) |
|
||||||
# Add two edges between the grid and P |
|
||||||
G.add_edge(new_node + 1, nodes[0]) |
|
||||||
G.add_edge(new_node, nodes[1]) |
|
||||||
# K5 is 4-connected |
|
||||||
K = nx.complete_graph(5) |
|
||||||
G = nx.disjoint_union(G, K) |
|
||||||
# Add three edges between P and K5 |
|
||||||
G.add_edge(new_node + 2, new_node + 11) |
|
||||||
G.add_edge(new_node + 3, new_node + 12) |
|
||||||
G.add_edge(new_node + 4, new_node + 13) |
|
||||||
# Add another K5 sharing a node |
|
||||||
G = nx.disjoint_union(G, K) |
|
||||||
nbrs = G[new_node + 10] |
|
||||||
G.remove_node(new_node + 10) |
|
||||||
for nbr in nbrs: |
|
||||||
G.add_edge(new_node + 17, nbr) |
|
||||||
# Commenting this makes the graph not biconnected !! |
|
||||||
# This stupid mistake make one reviewer very angry :P |
|
||||||
G.add_edge(new_node + 16, new_node + 8) |
|
||||||
|
|
||||||
for nodes in [(labels[(0, 0)], labels[(1, 0)]), (labels[(3, 0)], labels[(4, 0)])]: |
|
||||||
new_node = G.order() + 1 |
|
||||||
# Petersen graph is triconnected |
|
||||||
P = nx.petersen_graph() |
|
||||||
G = nx.disjoint_union(G, P) |
|
||||||
# Add two edges between the grid and P |
|
||||||
G.add_edge(new_node + 1, nodes[0]) |
|
||||||
G.add_edge(new_node, nodes[1]) |
|
||||||
# K5 is 4-connected |
|
||||||
K = nx.complete_graph(5) |
|
||||||
G = nx.disjoint_union(G, K) |
|
||||||
# Add three edges between P and K5 |
|
||||||
G.add_edge(new_node + 2, new_node + 11) |
|
||||||
G.add_edge(new_node + 3, new_node + 12) |
|
||||||
G.add_edge(new_node + 4, new_node + 13) |
|
||||||
# Add another K5 sharing two nodes |
|
||||||
G = nx.disjoint_union(G, K) |
|
||||||
nbrs = G[new_node + 10] |
|
||||||
G.remove_node(new_node + 10) |
|
||||||
for nbr in nbrs: |
|
||||||
G.add_edge(new_node + 17, nbr) |
|
||||||
nbrs2 = G[new_node + 9] |
|
||||||
G.remove_node(new_node + 9) |
|
||||||
for nbr in nbrs2: |
|
||||||
G.add_edge(new_node + 18, nbr) |
|
||||||
return G |
|
||||||
|
|
||||||
|
|
||||||
# Helper function |
|
||||||
|
|
||||||
|
|
||||||
def _check_connectivity(G): |
|
||||||
result = k_components(G) |
|
||||||
for k, components in result.items(): |
|
||||||
if k < 3: |
|
||||||
continue |
|
||||||
for component in components: |
|
||||||
C = G.subgraph(component) |
|
||||||
K = nx.node_connectivity(C) |
|
||||||
assert K >= k |
|
||||||
|
|
||||||
|
|
||||||
def test_torrents_and_ferraro_graph(): |
|
||||||
G = torrents_and_ferraro_graph() |
|
||||||
_check_connectivity(G) |
|
||||||
|
|
||||||
|
|
||||||
def test_example_1(): |
|
||||||
G = graph_example_1() |
|
||||||
_check_connectivity(G) |
|
||||||
|
|
||||||
|
|
||||||
def test_karate_0(): |
|
||||||
G = nx.karate_club_graph() |
|
||||||
_check_connectivity(G) |
|
||||||
|
|
||||||
|
|
||||||
def test_karate_1(): |
|
||||||
karate_k_num = { |
|
||||||
0: 4, |
|
||||||
1: 4, |
|
||||||
2: 4, |
|
||||||
3: 4, |
|
||||||
4: 3, |
|
||||||
5: 3, |
|
||||||
6: 3, |
|
||||||
7: 4, |
|
||||||
8: 4, |
|
||||||
9: 2, |
|
||||||
10: 3, |
|
||||||
11: 1, |
|
||||||
12: 2, |
|
||||||
13: 4, |
|
||||||
14: 2, |
|
||||||
15: 2, |
|
||||||
16: 2, |
|
||||||
17: 2, |
|
||||||
18: 2, |
|
||||||
19: 3, |
|
||||||
20: 2, |
|
||||||
21: 2, |
|
||||||
22: 2, |
|
||||||
23: 3, |
|
||||||
24: 3, |
|
||||||
25: 3, |
|
||||||
26: 2, |
|
||||||
27: 3, |
|
||||||
28: 3, |
|
||||||
29: 3, |
|
||||||
30: 4, |
|
||||||
31: 3, |
|
||||||
32: 4, |
|
||||||
33: 4, |
|
||||||
} |
|
||||||
approx_karate_k_num = karate_k_num.copy() |
|
||||||
approx_karate_k_num[24] = 2 |
|
||||||
approx_karate_k_num[25] = 2 |
|
||||||
G = nx.karate_club_graph() |
|
||||||
k_comps = k_components(G) |
|
||||||
k_num = build_k_number_dict(k_comps) |
|
||||||
assert k_num in (karate_k_num, approx_karate_k_num) |
|
||||||
|
|
||||||
|
|
||||||
def test_example_1_detail_3_and_4(): |
|
||||||
G = graph_example_1() |
|
||||||
result = k_components(G) |
|
||||||
# In this example graph there are 8 3-components, 4 with 15 nodes |
|
||||||
# and 4 with 5 nodes. |
|
||||||
assert len(result[3]) == 8 |
|
||||||
assert len([c for c in result[3] if len(c) == 15]) == 4 |
|
||||||
assert len([c for c in result[3] if len(c) == 5]) == 4 |
|
||||||
# There are also 8 4-components all with 5 nodes. |
|
||||||
assert len(result[4]) == 8 |
|
||||||
assert all(len(c) == 5 for c in result[4]) |
|
||||||
# Finally check that the k-components detected have actually node |
|
||||||
# connectivity >= k. |
|
||||||
for k, components in result.items(): |
|
||||||
if k < 3: |
|
||||||
continue |
|
||||||
for component in components: |
|
||||||
K = nx.node_connectivity(G.subgraph(component)) |
|
||||||
assert K >= k |
|
||||||
|
|
||||||
|
|
||||||
def test_directed(): |
|
||||||
with pytest.raises(nx.NetworkXNotImplemented): |
|
||||||
G = nx.gnp_random_graph(10, 0.4, directed=True) |
|
||||||
kc = k_components(G) |
|
||||||
|
|
||||||
|
|
||||||
def test_same(): |
|
||||||
equal = {"A": 2, "B": 2, "C": 2} |
|
||||||
slightly_different = {"A": 2, "B": 1, "C": 2} |
|
||||||
different = {"A": 2, "B": 8, "C": 18} |
|
||||||
assert _same(equal) |
|
||||||
assert not _same(slightly_different) |
|
||||||
assert _same(slightly_different, tol=1) |
|
||||||
assert not _same(different) |
|
||||||
assert not _same(different, tol=4) |
|
||||||
|
|
||||||
|
|
||||||
class TestAntiGraph: |
|
||||||
@classmethod |
|
||||||
def setup_class(cls): |
|
||||||
cls.Gnp = nx.gnp_random_graph(20, 0.8) |
|
||||||
cls.Anp = _AntiGraph(nx.complement(cls.Gnp)) |
|
||||||
cls.Gd = nx.davis_southern_women_graph() |
|
||||||
cls.Ad = _AntiGraph(nx.complement(cls.Gd)) |
|
||||||
cls.Gk = nx.karate_club_graph() |
|
||||||
cls.Ak = _AntiGraph(nx.complement(cls.Gk)) |
|
||||||
cls.GA = [(cls.Gnp, cls.Anp), (cls.Gd, cls.Ad), (cls.Gk, cls.Ak)] |
|
||||||
|
|
||||||
def test_size(self): |
|
||||||
for G, A in self.GA: |
|
||||||
n = G.order() |
|
||||||
s = len(list(G.edges())) + len(list(A.edges())) |
|
||||||
assert s == (n * (n - 1)) / 2 |
|
||||||
|
|
||||||
def test_degree(self): |
|
||||||
for G, A in self.GA: |
|
||||||
assert sorted(G.degree()) == sorted(A.degree()) |
|
||||||
|
|
||||||
def test_core_number(self): |
|
||||||
for G, A in self.GA: |
|
||||||
assert nx.core_number(G) == nx.core_number(A) |
|
||||||
|
|
||||||
def test_connected_components(self): |
|
||||||
for G, A in self.GA: |
|
||||||
gc = [set(c) for c in nx.connected_components(G)] |
|
||||||
ac = [set(c) for c in nx.connected_components(A)] |
|
||||||
for comp in ac: |
|
||||||
assert comp in gc |
|
||||||
|
|
||||||
def test_adj(self): |
|
||||||
for G, A in self.GA: |
|
||||||
for n, nbrs in G.adj.items(): |
|
||||||
a_adj = sorted((n, sorted(ad)) for n, ad in A.adj.items()) |
|
||||||
g_adj = sorted((n, sorted(ad)) for n, ad in G.adj.items()) |
|
||||||
assert a_adj == g_adj |
|
||||||
|
|
||||||
def test_adjacency(self): |
|
||||||
for G, A in self.GA: |
|
||||||
a_adj = list(A.adjacency()) |
|
||||||
for n, nbrs in G.adjacency(): |
|
||||||
assert (n, set(nbrs)) in a_adj |
|
||||||
|
|
||||||
def test_neighbors(self): |
|
||||||
for G, A in self.GA: |
|
||||||
node = list(G.nodes())[0] |
|
||||||
assert set(G.neighbors(node)) == set(A.neighbors(node)) |
|
||||||
|
|
||||||
def test_node_not_in_graph(self): |
|
||||||
for G, A in self.GA: |
|
||||||
node = "non_existent_node" |
|
||||||
pytest.raises(nx.NetworkXError, A.neighbors, node) |
|
||||||
pytest.raises(nx.NetworkXError, G.neighbors, node) |
|
||||||
|
|
||||||
def test_degree_thingraph(self): |
|
||||||
for G, A in self.GA: |
|
||||||
node = list(G.nodes())[0] |
|
||||||
nodes = list(G.nodes())[1:4] |
|
||||||
assert G.degree(node) == A.degree(node) |
|
||||||
assert sum(d for n, d in G.degree()) == sum(d for n, d in A.degree()) |
|
||||||
# AntiGraph is a ThinGraph, so all the weights are 1 |
|
||||||
assert sum(d for n, d in A.degree()) == sum( |
|
||||||
d for n, d in A.degree(weight="weight") |
|
||||||
) |
|
||||||
assert sum(d for n, d in G.degree(nodes)) == sum( |
|
||||||
d for n, d in A.degree(nodes) |
|
||||||
) |
|
@ -1,8 +0,0 @@ |
|||||||
import networkx as nx |
|
||||||
import networkx.algorithms.approximation as a |
|
||||||
|
|
||||||
|
|
||||||
def test_min_maximal_matching(): |
|
||||||
# smoke test |
|
||||||
G = nx.Graph() |
|
||||||
assert len(a.min_maximal_matching(G)) == 0 |
|
@ -1,31 +0,0 @@ |
|||||||
import networkx as nx |
|
||||||
import networkx.algorithms.approximation as apxa |
|
||||||
|
|
||||||
|
|
||||||
def test_ramsey(): |
|
||||||
# this should only find the complete graph |
|
||||||
graph = nx.complete_graph(10) |
|
||||||
c, i = apxa.ramsey_R2(graph) |
|
||||||
cdens = nx.density(graph.subgraph(c)) |
|
||||||
assert cdens == 1.0, "clique not correctly found by ramsey!" |
|
||||||
idens = nx.density(graph.subgraph(i)) |
|
||||||
assert idens == 0.0, "i-set not correctly found by ramsey!" |
|
||||||
|
|
||||||
# this trival graph has no cliques. should just find i-sets |
|
||||||
graph = nx.trivial_graph() |
|
||||||
c, i = apxa.ramsey_R2(graph) |
|
||||||
assert c == {0}, "clique not correctly found by ramsey!" |
|
||||||
assert i == {0}, "i-set not correctly found by ramsey!" |
|
||||||
|
|
||||||
graph = nx.barbell_graph(10, 5, nx.Graph()) |
|
||||||
c, i = apxa.ramsey_R2(graph) |
|
||||||
cdens = nx.density(graph.subgraph(c)) |
|
||||||
assert cdens == 1.0, "clique not correctly found by ramsey!" |
|
||||||
idens = nx.density(graph.subgraph(i)) |
|
||||||
assert idens == 0.0, "i-set not correctly found by ramsey!" |
|
||||||
|
|
||||||
# add self-loops and test again |
|
||||||
graph.add_edges_from([(n, n) for n in range(0, len(graph), 2)]) |
|
||||||
cc, ii = apxa.ramsey_R2(graph) |
|
||||||
assert cc == c |
|
||||||
assert ii == i |
|
@ -1,83 +0,0 @@ |
|||||||
import pytest |
|
||||||
import networkx as nx |
|
||||||
from networkx.algorithms.approximation.steinertree import metric_closure |
|
||||||
from networkx.algorithms.approximation.steinertree import steiner_tree |
|
||||||
from networkx.testing.utils import assert_edges_equal |
|
||||||
|
|
||||||
|
|
||||||
class TestSteinerTree: |
|
||||||
@classmethod |
|
||||||
def setup_class(cls): |
|
||||||
G = nx.Graph() |
|
||||||
G.add_edge(1, 2, weight=10) |
|
||||||
G.add_edge(2, 3, weight=10) |
|
||||||
G.add_edge(3, 4, weight=10) |
|
||||||
G.add_edge(4, 5, weight=10) |
|
||||||
G.add_edge(5, 6, weight=10) |
|
||||||
G.add_edge(2, 7, weight=1) |
|
||||||
G.add_edge(7, 5, weight=1) |
|
||||||
cls.G = G |
|
||||||
cls.term_nodes = [1, 2, 3, 4, 5] |
|
||||||
|
|
||||||
def test_connected_metric_closure(self): |
|
||||||
G = self.G.copy() |
|
||||||
G.add_node(100) |
|
||||||
pytest.raises(nx.NetworkXError, metric_closure, G) |
|
||||||
|
|
||||||
def test_metric_closure(self): |
|
||||||
M = metric_closure(self.G) |
|
||||||
mc = [ |
|
||||||
(1, 2, {"distance": 10, "path": [1, 2]}), |
|
||||||
(1, 3, {"distance": 20, "path": [1, 2, 3]}), |
|
||||||
(1, 4, {"distance": 22, "path": [1, 2, 7, 5, 4]}), |
|
||||||
(1, 5, {"distance": 12, "path": [1, 2, 7, 5]}), |
|
||||||
(1, 6, {"distance": 22, "path": [1, 2, 7, 5, 6]}), |
|
||||||
(1, 7, {"distance": 11, "path": [1, 2, 7]}), |
|
||||||
(2, 3, {"distance": 10, "path": [2, 3]}), |
|
||||||
(2, 4, {"distance": 12, "path": [2, 7, 5, 4]}), |
|
||||||
(2, 5, {"distance": 2, "path": [2, 7, 5]}), |
|
||||||
(2, 6, {"distance": 12, "path": [2, 7, 5, 6]}), |
|
||||||
(2, 7, {"distance": 1, "path": [2, 7]}), |
|
||||||
(3, 4, {"distance": 10, "path": [3, 4]}), |
|
||||||
(3, 5, {"distance": 12, "path": [3, 2, 7, 5]}), |
|
||||||
(3, 6, {"distance": 22, "path": [3, 2, 7, 5, 6]}), |
|
||||||
(3, 7, {"distance": 11, "path": [3, 2, 7]}), |
|
||||||
(4, 5, {"distance": 10, "path": [4, 5]}), |
|
||||||
(4, 6, {"distance": 20, "path": [4, 5, 6]}), |
|
||||||
(4, 7, {"distance": 11, "path": [4, 5, 7]}), |
|
||||||
(5, 6, {"distance": 10, "path": [5, 6]}), |
|
||||||
(5, 7, {"distance": 1, "path": [5, 7]}), |
|
||||||
(6, 7, {"distance": 11, "path": [6, 5, 7]}), |
|
||||||
] |
|
||||||
assert_edges_equal(list(M.edges(data=True)), mc) |
|
||||||
|
|
||||||
def test_steiner_tree(self): |
|
||||||
S = steiner_tree(self.G, self.term_nodes) |
|
||||||
expected_steiner_tree = [ |
|
||||||
(1, 2, {"weight": 10}), |
|
||||||
(2, 3, {"weight": 10}), |
|
||||||
(2, 7, {"weight": 1}), |
|
||||||
(3, 4, {"weight": 10}), |
|
||||||
(5, 7, {"weight": 1}), |
|
||||||
] |
|
||||||
assert_edges_equal(list(S.edges(data=True)), expected_steiner_tree) |
|
||||||
|
|
||||||
def test_multigraph_steiner_tree(self): |
|
||||||
G = nx.MultiGraph() |
|
||||||
G.add_edges_from( |
|
||||||
[ |
|
||||||
(1, 2, 0, {"weight": 1}), |
|
||||||
(2, 3, 0, {"weight": 999}), |
|
||||||
(2, 3, 1, {"weight": 1}), |
|
||||||
(3, 4, 0, {"weight": 1}), |
|
||||||
(3, 5, 0, {"weight": 1}), |
|
||||||
] |
|
||||||
) |
|
||||||
terminal_nodes = [2, 4, 5] |
|
||||||
expected_edges = [ |
|
||||||
(2, 3, 1, {"weight": 1}), # edge with key 1 has lower weight |
|
||||||
(3, 4, 0, {"weight": 1}), |
|
||||||
(3, 5, 0, {"weight": 1}), |
|
||||||
] |
|
||||||
T = steiner_tree(G, terminal_nodes) |
|
||||||
assert_edges_equal(T.edges(data=True, keys=True), expected_edges) |
|
@ -1,269 +0,0 @@ |
|||||||
import networkx as nx |
|
||||||
from networkx.algorithms.approximation import treewidth_min_degree |
|
||||||
from networkx.algorithms.approximation import treewidth_min_fill_in |
|
||||||
from networkx.algorithms.approximation.treewidth import min_fill_in_heuristic |
|
||||||
from networkx.algorithms.approximation.treewidth import MinDegreeHeuristic |
|
||||||
import itertools |
|
||||||
|
|
||||||
|
|
||||||
def is_tree_decomp(graph, decomp): |
|
||||||
"""Check if the given tree decomposition is valid.""" |
|
||||||
for x in graph.nodes(): |
|
||||||
appear_once = False |
|
||||||
for bag in decomp.nodes(): |
|
||||||
if x in bag: |
|
||||||
appear_once = True |
|
||||||
break |
|
||||||
assert appear_once |
|
||||||
|
|
||||||
# Check if each connected pair of nodes are at least once together in a bag |
|
||||||
for (x, y) in graph.edges(): |
|
||||||
appear_together = False |
|
||||||
for bag in decomp.nodes(): |
|
||||||
if x in bag and y in bag: |
|
||||||
appear_together = True |
|
||||||
break |
|
||||||
assert appear_together |
|
||||||
|
|
||||||
# Check if the nodes associated with vertex v form a connected subset of T |
|
||||||
for v in graph.nodes(): |
|
||||||
subset = [] |
|
||||||
for bag in decomp.nodes(): |
|
||||||
if v in bag: |
|
||||||
subset.append(bag) |
|
||||||
sub_graph = decomp.subgraph(subset) |
|
||||||
assert nx.is_connected(sub_graph) |
|
||||||
|
|
||||||
|
|
||||||
class TestTreewidthMinDegree: |
|
||||||
"""Unit tests for the min_degree function""" |
|
||||||
|
|
||||||
@classmethod |
|
||||||
def setup_class(cls): |
|
||||||
"""Setup for different kinds of trees""" |
|
||||||
cls.complete = nx.Graph() |
|
||||||
cls.complete.add_edge(1, 2) |
|
||||||
cls.complete.add_edge(2, 3) |
|
||||||
cls.complete.add_edge(1, 3) |
|
||||||
|
|
||||||
cls.small_tree = nx.Graph() |
|
||||||
cls.small_tree.add_edge(1, 3) |
|
||||||
cls.small_tree.add_edge(4, 3) |
|
||||||
cls.small_tree.add_edge(2, 3) |
|
||||||
cls.small_tree.add_edge(3, 5) |
|
||||||
cls.small_tree.add_edge(5, 6) |
|
||||||
cls.small_tree.add_edge(5, 7) |
|
||||||
cls.small_tree.add_edge(6, 7) |
|
||||||
|
|
||||||
cls.deterministic_graph = nx.Graph() |
|
||||||
cls.deterministic_graph.add_edge(0, 1) # deg(0) = 1 |
|
||||||
|
|
||||||
cls.deterministic_graph.add_edge(1, 2) # deg(1) = 2 |
|
||||||
|
|
||||||
cls.deterministic_graph.add_edge(2, 3) |
|
||||||
cls.deterministic_graph.add_edge(2, 4) # deg(2) = 3 |
|
||||||
|
|
||||||
cls.deterministic_graph.add_edge(3, 4) |
|
||||||
cls.deterministic_graph.add_edge(3, 5) |
|
||||||
cls.deterministic_graph.add_edge(3, 6) # deg(3) = 4 |
|
||||||
|
|
||||||
cls.deterministic_graph.add_edge(4, 5) |
|
||||||
cls.deterministic_graph.add_edge(4, 6) |
|
||||||
cls.deterministic_graph.add_edge(4, 7) # deg(4) = 5 |
|
||||||
|
|
||||||
cls.deterministic_graph.add_edge(5, 6) |
|
||||||
cls.deterministic_graph.add_edge(5, 7) |
|
||||||
cls.deterministic_graph.add_edge(5, 8) |
|
||||||
cls.deterministic_graph.add_edge(5, 9) # deg(5) = 6 |
|
||||||
|
|
||||||
cls.deterministic_graph.add_edge(6, 7) |
|
||||||
cls.deterministic_graph.add_edge(6, 8) |
|
||||||
cls.deterministic_graph.add_edge(6, 9) # deg(6) = 6 |
|
||||||
|
|
||||||
cls.deterministic_graph.add_edge(7, 8) |
|
||||||
cls.deterministic_graph.add_edge(7, 9) # deg(7) = 5 |
|
||||||
|
|
||||||
cls.deterministic_graph.add_edge(8, 9) # deg(8) = 4 |
|
||||||
|
|
||||||
def test_petersen_graph(self): |
|
||||||
"""Test Petersen graph tree decomposition result""" |
|
||||||
G = nx.petersen_graph() |
|
||||||
_, decomp = treewidth_min_degree(G) |
|
||||||
is_tree_decomp(G, decomp) |
|
||||||
|
|
||||||
def test_small_tree_treewidth(self): |
|
||||||
"""Test small tree |
|
||||||
|
|
||||||
Test if the computed treewidth of the known self.small_tree is 2. |
|
||||||
As we know which value we can expect from our heuristic, values other |
|
||||||
than two are regressions |
|
||||||
""" |
|
||||||
G = self.small_tree |
|
||||||
# the order of removal should be [1,2,4]3[5,6,7] |
|
||||||
# (with [] denoting any order of the containing nodes) |
|
||||||
# resulting in treewidth 2 for the heuristic |
|
||||||
treewidth, _ = treewidth_min_fill_in(G) |
|
||||||
assert treewidth == 2 |
|
||||||
|
|
||||||
def test_heuristic_abort(self): |
|
||||||
"""Test heuristic abort condition for fully connected graph""" |
|
||||||
graph = {} |
|
||||||
for u in self.complete: |
|
||||||
graph[u] = set() |
|
||||||
for v in self.complete[u]: |
|
||||||
if u != v: # ignore self-loop |
|
||||||
graph[u].add(v) |
|
||||||
|
|
||||||
deg_heuristic = MinDegreeHeuristic(graph) |
|
||||||
node = deg_heuristic.best_node(graph) |
|
||||||
if node is None: |
|
||||||
pass |
|
||||||
else: |
|
||||||
assert False |
|
||||||
|
|
||||||
def test_empty_graph(self): |
|
||||||
"""Test empty graph""" |
|
||||||
G = nx.Graph() |
|
||||||
_, _ = treewidth_min_degree(G) |
|
||||||
|
|
||||||
def test_two_component_graph(self): |
|
||||||
"""Test empty graph""" |
|
||||||
G = nx.Graph() |
|
||||||
G.add_node(1) |
|
||||||
G.add_node(2) |
|
||||||
treewidth, _ = treewidth_min_degree(G) |
|
||||||
assert treewidth == 0 |
|
||||||
|
|
||||||
def test_heuristic_first_steps(self): |
|
||||||
"""Test first steps of min_degree heuristic""" |
|
||||||
graph = { |
|
||||||
n: set(self.deterministic_graph[n]) - {n} for n in self.deterministic_graph |
|
||||||
} |
|
||||||
deg_heuristic = MinDegreeHeuristic(graph) |
|
||||||
elim_node = deg_heuristic.best_node(graph) |
|
||||||
print(f"Graph {graph}:") |
|
||||||
steps = [] |
|
||||||
|
|
||||||
while elim_node is not None: |
|
||||||
print(f"Removing {elim_node}:") |
|
||||||
steps.append(elim_node) |
|
||||||
nbrs = graph[elim_node] |
|
||||||
|
|
||||||
for u, v in itertools.permutations(nbrs, 2): |
|
||||||
if v not in graph[u]: |
|
||||||
graph[u].add(v) |
|
||||||
|
|
||||||
for u in graph: |
|
||||||
if elim_node in graph[u]: |
|
||||||
graph[u].remove(elim_node) |
|
||||||
|
|
||||||
del graph[elim_node] |
|
||||||
print(f"Graph {graph}:") |
|
||||||
elim_node = deg_heuristic.best_node(graph) |
|
||||||
|
|
||||||
# check only the first 5 elements for equality |
|
||||||
assert steps[:5] == [0, 1, 2, 3, 4] |
|
||||||
|
|
||||||
|
|
||||||
class TestTreewidthMinFillIn: |
|
||||||
"""Unit tests for the treewidth_min_fill_in function.""" |
|
||||||
|
|
||||||
@classmethod |
|
||||||
def setup_class(cls): |
|
||||||
"""Setup for different kinds of trees""" |
|
||||||
cls.complete = nx.Graph() |
|
||||||
cls.complete.add_edge(1, 2) |
|
||||||
cls.complete.add_edge(2, 3) |
|
||||||
cls.complete.add_edge(1, 3) |
|
||||||
|
|
||||||
cls.small_tree = nx.Graph() |
|
||||||
cls.small_tree.add_edge(1, 2) |
|
||||||
cls.small_tree.add_edge(2, 3) |
|
||||||
cls.small_tree.add_edge(3, 4) |
|
||||||
cls.small_tree.add_edge(1, 4) |
|
||||||
cls.small_tree.add_edge(2, 4) |
|
||||||
cls.small_tree.add_edge(4, 5) |
|
||||||
cls.small_tree.add_edge(5, 6) |
|
||||||
cls.small_tree.add_edge(5, 7) |
|
||||||
cls.small_tree.add_edge(6, 7) |
|
||||||
|
|
||||||
cls.deterministic_graph = nx.Graph() |
|
||||||
cls.deterministic_graph.add_edge(1, 2) |
|
||||||
cls.deterministic_graph.add_edge(1, 3) |
|
||||||
cls.deterministic_graph.add_edge(3, 4) |
|
||||||
cls.deterministic_graph.add_edge(2, 4) |
|
||||||
cls.deterministic_graph.add_edge(3, 5) |
|
||||||
cls.deterministic_graph.add_edge(4, 5) |
|
||||||
cls.deterministic_graph.add_edge(3, 6) |
|
||||||
cls.deterministic_graph.add_edge(5, 6) |
|
||||||
|
|
||||||
def test_petersen_graph(self): |
|
||||||
"""Test Petersen graph tree decomposition result""" |
|
||||||
G = nx.petersen_graph() |
|
||||||
_, decomp = treewidth_min_fill_in(G) |
|
||||||
is_tree_decomp(G, decomp) |
|
||||||
|
|
||||||
def test_small_tree_treewidth(self): |
|
||||||
"""Test if the computed treewidth of the known self.small_tree is 2""" |
|
||||||
G = self.small_tree |
|
||||||
# the order of removal should be [1,2,4]3[5,6,7] |
|
||||||
# (with [] denoting any order of the containing nodes) |
|
||||||
# resulting in treewidth 2 for the heuristic |
|
||||||
treewidth, _ = treewidth_min_fill_in(G) |
|
||||||
assert treewidth == 2 |
|
||||||
|
|
||||||
def test_heuristic_abort(self): |
|
||||||
"""Test if min_fill_in returns None for fully connected graph""" |
|
||||||
graph = {} |
|
||||||
for u in self.complete: |
|
||||||
graph[u] = set() |
|
||||||
for v in self.complete[u]: |
|
||||||
if u != v: # ignore self-loop |
|
||||||
graph[u].add(v) |
|
||||||
next_node = min_fill_in_heuristic(graph) |
|
||||||
if next_node is None: |
|
||||||
pass |
|
||||||
else: |
|
||||||
assert False |
|
||||||
|
|
||||||
def test_empty_graph(self): |
|
||||||
"""Test empty graph""" |
|
||||||
G = nx.Graph() |
|
||||||
_, _ = treewidth_min_fill_in(G) |
|
||||||
|
|
||||||
def test_two_component_graph(self): |
|
||||||
"""Test empty graph""" |
|
||||||
G = nx.Graph() |
|
||||||
G.add_node(1) |
|
||||||
G.add_node(2) |
|
||||||
treewidth, _ = treewidth_min_fill_in(G) |
|
||||||
assert treewidth == 0 |
|
||||||
|
|
||||||
def test_heuristic_first_steps(self): |
|
||||||
"""Test first steps of min_fill_in heuristic""" |
|
||||||
graph = { |
|
||||||
n: set(self.deterministic_graph[n]) - {n} for n in self.deterministic_graph |
|
||||||
} |
|
||||||
print(f"Graph {graph}:") |
|
||||||
elim_node = min_fill_in_heuristic(graph) |
|
||||||
steps = [] |
|
||||||
|
|
||||||
while elim_node is not None: |
|
||||||
print(f"Removing {elim_node}:") |
|
||||||
steps.append(elim_node) |
|
||||||
nbrs = graph[elim_node] |
|
||||||
|
|
||||||
for u, v in itertools.permutations(nbrs, 2): |
|
||||||
if v not in graph[u]: |
|
||||||
graph[u].add(v) |
|
||||||
|
|
||||||
for u in graph: |
|
||||||
if elim_node in graph[u]: |
|
||||||
graph[u].remove(elim_node) |
|
||||||
|
|
||||||
del graph[elim_node] |
|
||||||
print(f"Graph {graph}:") |
|
||||||
elim_node = min_fill_in_heuristic(graph) |
|
||||||
|
|
||||||
# check only the first 2 elements for equality |
|
||||||
assert steps[:2] == [6, 5] |
|
@ -1,55 +0,0 @@ |
|||||||
import networkx as nx |
|
||||||
from networkx.algorithms.approximation import min_weighted_vertex_cover |
|
||||||
|
|
||||||
|
|
||||||
def is_cover(G, node_cover): |
|
||||||
return all({u, v} & node_cover for u, v in G.edges()) |
|
||||||
|
|
||||||
|
|
||||||
class TestMWVC: |
|
||||||
"""Unit tests for the approximate minimum weighted vertex cover |
|
||||||
function, |
|
||||||
:func:`~networkx.algorithms.approximation.vertex_cover.min_weighted_vertex_cover`. |
|
||||||
|
|
||||||
""" |
|
||||||
|
|
||||||
def test_unweighted_directed(self): |
|
||||||
# Create a star graph in which half the nodes are directed in |
|
||||||
# and half are directed out. |
|
||||||
G = nx.DiGraph() |
|
||||||
G.add_edges_from((0, v) for v in range(1, 26)) |
|
||||||
G.add_edges_from((v, 0) for v in range(26, 51)) |
|
||||||
cover = min_weighted_vertex_cover(G) |
|
||||||
assert 2 == len(cover) |
|
||||||
assert is_cover(G, cover) |
|
||||||
|
|
||||||
def test_unweighted_undirected(self): |
|
||||||
# create a simple star graph |
|
||||||
size = 50 |
|
||||||
sg = nx.star_graph(size) |
|
||||||
cover = min_weighted_vertex_cover(sg) |
|
||||||
assert 2 == len(cover) |
|
||||||
assert is_cover(sg, cover) |
|
||||||
|
|
||||||
def test_weighted(self): |
|
||||||
wg = nx.Graph() |
|
||||||
wg.add_node(0, weight=10) |
|
||||||
wg.add_node(1, weight=1) |
|
||||||
wg.add_node(2, weight=1) |
|
||||||
wg.add_node(3, weight=1) |
|
||||||
wg.add_node(4, weight=1) |
|
||||||
|
|
||||||
wg.add_edge(0, 1) |
|
||||||
wg.add_edge(0, 2) |
|
||||||
wg.add_edge(0, 3) |
|
||||||
wg.add_edge(0, 4) |
|
||||||
|
|
||||||
wg.add_edge(1, 2) |
|
||||||
wg.add_edge(2, 3) |
|
||||||
wg.add_edge(3, 4) |
|
||||||
wg.add_edge(4, 1) |
|
||||||
|
|
||||||
cover = min_weighted_vertex_cover(wg, weight="weight") |
|
||||||
csum = sum(wg.nodes[node]["weight"] for node in cover) |
|
||||||
assert 4 == csum |
|
||||||
assert is_cover(wg, cover) |
|
@ -1,249 +0,0 @@ |
|||||||
"""Functions for computing treewidth decomposition. |
|
||||||
|
|
||||||
Treewidth of an undirected graph is a number associated with the graph. |
|
||||||
It can be defined as the size of the largest vertex set (bag) in a tree |
|
||||||
decomposition of the graph minus one. |
|
||||||
|
|
||||||
`Wikipedia: Treewidth <https://en.wikipedia.org/wiki/Treewidth>`_ |
|
||||||
|
|
||||||
The notions of treewidth and tree decomposition have gained their |
|
||||||
attractiveness partly because many graph and network problems that are |
|
||||||
intractable (e.g., NP-hard) on arbitrary graphs become efficiently |
|
||||||
solvable (e.g., with a linear time algorithm) when the treewidth of the |
|
||||||
input graphs is bounded by a constant [1]_ [2]_. |
|
||||||
|
|
||||||
There are two different functions for computing a tree decomposition: |
|
||||||
:func:`treewidth_min_degree` and :func:`treewidth_min_fill_in`. |
|
||||||
|
|
||||||
.. [1] Hans L. Bodlaender and Arie M. C. A. Koster. 2010. "Treewidth |
|
||||||
computations I.Upper bounds". Inf. Comput. 208, 3 (March 2010),259-275. |
|
||||||
http://dx.doi.org/10.1016/j.ic.2009.03.008 |
|
||||||
|
|
||||||
.. [2] Hans L. Bodlaender. "Discovering Treewidth". Institute of Information |
|
||||||
and Computing Sciences, Utrecht University. |
|
||||||
Technical Report UU-CS-2005-018. |
|
||||||
http://www.cs.uu.nl |
|
||||||
|
|
||||||
.. [3] K. Wang, Z. Lu, and J. Hicks *Treewidth*. |
|
||||||
http://web.eecs.utk.edu/~cphillip/cs594_spring2015_projects/treewidth.pdf |
|
||||||
|
|
||||||
""" |
|
||||||
|
|
||||||
import sys |
|
||||||
|
|
||||||
import networkx as nx |
|
||||||
from networkx.utils import not_implemented_for |
|
||||||
from heapq import heappush, heappop, heapify |
|
||||||
import itertools |
|
||||||
|
|
||||||
__all__ = ["treewidth_min_degree", "treewidth_min_fill_in"] |
|
||||||
|
|
||||||
|
|
||||||
@not_implemented_for("directed") |
|
||||||
@not_implemented_for("multigraph") |
|
||||||
def treewidth_min_degree(G): |
|
||||||
""" Returns a treewidth decomposition using the Minimum Degree heuristic. |
|
||||||
|
|
||||||
The heuristic chooses the nodes according to their degree, i.e., first |
|
||||||
the node with the lowest degree is chosen, then the graph is updated |
|
||||||
and the corresponding node is removed. Next, a new node with the lowest |
|
||||||
degree is chosen, and so on. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
Treewidth decomposition : (int, Graph) tuple |
|
||||||
2-tuple with treewidth and the corresponding decomposed tree. |
|
||||||
""" |
|
||||||
deg_heuristic = MinDegreeHeuristic(G) |
|
||||||
return treewidth_decomp(G, lambda graph: deg_heuristic.best_node(graph)) |
|
||||||
|
|
||||||
|
|
||||||
@not_implemented_for("directed") |
|
||||||
@not_implemented_for("multigraph") |
|
||||||
def treewidth_min_fill_in(G): |
|
||||||
""" Returns a treewidth decomposition using the Minimum Fill-in heuristic. |
|
||||||
|
|
||||||
The heuristic chooses a node from the graph, where the number of edges |
|
||||||
added turning the neighbourhood of the chosen node into clique is as |
|
||||||
small as possible. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
Treewidth decomposition : (int, Graph) tuple |
|
||||||
2-tuple with treewidth and the corresponding decomposed tree. |
|
||||||
""" |
|
||||||
return treewidth_decomp(G, min_fill_in_heuristic) |
|
||||||
|
|
||||||
|
|
||||||
class MinDegreeHeuristic: |
|
||||||
""" Implements the Minimum Degree heuristic. |
|
||||||
|
|
||||||
The heuristic chooses the nodes according to their degree |
|
||||||
(number of neighbours), i.e., first the node with the lowest degree is |
|
||||||
chosen, then the graph is updated and the corresponding node is |
|
||||||
removed. Next, a new node with the lowest degree is chosen, and so on. |
|
||||||
""" |
|
||||||
|
|
||||||
def __init__(self, graph): |
|
||||||
self._graph = graph |
|
||||||
|
|
||||||
# nodes that have to be updated in the heap before each iteration |
|
||||||
self._update_nodes = [] |
|
||||||
|
|
||||||
self._degreeq = [] # a heapq with 2-tuples (degree,node) |
|
||||||
|
|
||||||
# build heap with initial degrees |
|
||||||
for n in graph: |
|
||||||
self._degreeq.append((len(graph[n]), n)) |
|
||||||
heapify(self._degreeq) |
|
||||||
|
|
||||||
def best_node(self, graph): |
|
||||||
# update nodes in self._update_nodes |
|
||||||
for n in self._update_nodes: |
|
||||||
# insert changed degrees into degreeq |
|
||||||
heappush(self._degreeq, (len(graph[n]), n)) |
|
||||||
|
|
||||||
# get the next valid (minimum degree) node |
|
||||||
while self._degreeq: |
|
||||||
(min_degree, elim_node) = heappop(self._degreeq) |
|
||||||
if elim_node not in graph or len(graph[elim_node]) != min_degree: |
|
||||||
# outdated entry in degreeq |
|
||||||
continue |
|
||||||
elif min_degree == len(graph) - 1: |
|
||||||
# fully connected: abort condition |
|
||||||
return None |
|
||||||
|
|
||||||
# remember to update nodes in the heap before getting the next node |
|
||||||
self._update_nodes = graph[elim_node] |
|
||||||
return elim_node |
|
||||||
|
|
||||||
# the heap is empty: abort |
|
||||||
return None |
|
||||||
|
|
||||||
|
|
||||||
def min_fill_in_heuristic(graph): |
|
||||||
""" Implements the Minimum Degree heuristic. |
|
||||||
|
|
||||||
Returns the node from the graph, where the number of edges added when |
|
||||||
turning the neighbourhood of the chosen node into clique is as small as |
|
||||||
possible. This algorithm chooses the nodes using the Minimum Fill-In |
|
||||||
heuristic. The running time of the algorithm is :math:`O(V^3)` and it uses |
|
||||||
additional constant memory.""" |
|
||||||
|
|
||||||
if len(graph) == 0: |
|
||||||
return None |
|
||||||
|
|
||||||
min_fill_in_node = None |
|
||||||
|
|
||||||
min_fill_in = sys.maxsize |
|
||||||
|
|
||||||
# create sorted list of (degree, node) |
|
||||||
degree_list = [(len(graph[node]), node) for node in graph] |
|
||||||
degree_list.sort() |
|
||||||
|
|
||||||
# abort condition |
|
||||||
min_degree = degree_list[0][0] |
|
||||||
if min_degree == len(graph) - 1: |
|
||||||
return None |
|
||||||
|
|
||||||
for (_, node) in degree_list: |
|
||||||
num_fill_in = 0 |
|
||||||
nbrs = graph[node] |
|
||||||
for nbr in nbrs: |
|
||||||
# count how many nodes in nbrs current nbr is not connected to |
|
||||||
# subtract 1 for the node itself |
|
||||||
num_fill_in += len(nbrs - graph[nbr]) - 1 |
|
||||||
if num_fill_in >= 2 * min_fill_in: |
|
||||||
break |
|
||||||
|
|
||||||
num_fill_in /= 2 # divide by 2 because of double counting |
|
||||||
|
|
||||||
if num_fill_in < min_fill_in: # update min-fill-in node |
|
||||||
if num_fill_in == 0: |
|
||||||
return node |
|
||||||
min_fill_in = num_fill_in |
|
||||||
min_fill_in_node = node |
|
||||||
|
|
||||||
return min_fill_in_node |
|
||||||
|
|
||||||
|
|
||||||
def treewidth_decomp(G, heuristic=min_fill_in_heuristic): |
|
||||||
""" Returns a treewidth decomposition using the passed heuristic. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
heuristic : heuristic function |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
Treewidth decomposition : (int, Graph) tuple |
|
||||||
2-tuple with treewidth and the corresponding decomposed tree. |
|
||||||
""" |
|
||||||
|
|
||||||
# make dict-of-sets structure |
|
||||||
graph = {n: set(G[n]) - {n} for n in G} |
|
||||||
|
|
||||||
# stack containing nodes and neighbors in the order from the heuristic |
|
||||||
node_stack = [] |
|
||||||
|
|
||||||
# get first node from heuristic |
|
||||||
elim_node = heuristic(graph) |
|
||||||
while elim_node is not None: |
|
||||||
# connect all neighbours with each other |
|
||||||
nbrs = graph[elim_node] |
|
||||||
for u, v in itertools.permutations(nbrs, 2): |
|
||||||
if v not in graph[u]: |
|
||||||
graph[u].add(v) |
|
||||||
|
|
||||||
# push node and its current neighbors on stack |
|
||||||
node_stack.append((elim_node, nbrs)) |
|
||||||
|
|
||||||
# remove node from graph |
|
||||||
for u in graph[elim_node]: |
|
||||||
graph[u].remove(elim_node) |
|
||||||
|
|
||||||
del graph[elim_node] |
|
||||||
elim_node = heuristic(graph) |
|
||||||
|
|
||||||
# the abort condition is met; put all remaining nodes into one bag |
|
||||||
decomp = nx.Graph() |
|
||||||
first_bag = frozenset(graph.keys()) |
|
||||||
decomp.add_node(first_bag) |
|
||||||
|
|
||||||
treewidth = len(first_bag) - 1 |
|
||||||
|
|
||||||
while node_stack: |
|
||||||
# get node and its neighbors from the stack |
|
||||||
(curr_node, nbrs) = node_stack.pop() |
|
||||||
|
|
||||||
# find a bag all neighbors are in |
|
||||||
old_bag = None |
|
||||||
for bag in decomp.nodes: |
|
||||||
if nbrs <= bag: |
|
||||||
old_bag = bag |
|
||||||
break |
|
||||||
|
|
||||||
if old_bag is None: |
|
||||||
# no old_bag was found: just connect to the first_bag |
|
||||||
old_bag = first_bag |
|
||||||
|
|
||||||
# create new node for decomposition |
|
||||||
nbrs.add(curr_node) |
|
||||||
new_bag = frozenset(nbrs) |
|
||||||
|
|
||||||
# update treewidth |
|
||||||
treewidth = max(treewidth, len(new_bag) - 1) |
|
||||||
|
|
||||||
# add edge to decomposition (implicitly also adds the new node) |
|
||||||
decomp.add_edge(old_bag, new_bag) |
|
||||||
|
|
||||||
return treewidth, decomp |
|
@ -1,74 +0,0 @@ |
|||||||
"""Functions for computing an approximate minimum weight vertex cover. |
|
||||||
|
|
||||||
A |vertex cover|_ is a subset of nodes such that each edge in the graph |
|
||||||
is incident to at least one node in the subset. |
|
||||||
|
|
||||||
.. _vertex cover: https://en.wikipedia.org/wiki/Vertex_cover |
|
||||||
.. |vertex cover| replace:: *vertex cover* |
|
||||||
|
|
||||||
""" |
|
||||||
|
|
||||||
__all__ = ["min_weighted_vertex_cover"] |
|
||||||
|
|
||||||
|
|
||||||
def min_weighted_vertex_cover(G, weight=None): |
|
||||||
r"""Returns an approximate minimum weighted vertex cover. |
|
||||||
|
|
||||||
The set of nodes returned by this function is guaranteed to be a |
|
||||||
vertex cover, and the total weight of the set is guaranteed to be at |
|
||||||
most twice the total weight of the minimum weight vertex cover. In |
|
||||||
other words, |
|
||||||
|
|
||||||
.. math:: |
|
||||||
|
|
||||||
w(S) \leq 2 * w(S^*), |
|
||||||
|
|
||||||
where $S$ is the vertex cover returned by this function, |
|
||||||
$S^*$ is the vertex cover of minimum weight out of all vertex |
|
||||||
covers of the graph, and $w$ is the function that computes the |
|
||||||
sum of the weights of each node in that given set. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
|
|
||||||
weight : string, optional (default = None) |
|
||||||
If None, every node has weight 1. If a string, use this node |
|
||||||
attribute as the node weight. A node without this attribute is |
|
||||||
assumed to have weight 1. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
min_weighted_cover : set |
|
||||||
Returns a set of nodes whose weight sum is no more than twice |
|
||||||
the weight sum of the minimum weight vertex cover. |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
For a directed graph, a vertex cover has the same definition: a set |
|
||||||
of nodes such that each edge in the graph is incident to at least |
|
||||||
one node in the set. Whether the node is the head or tail of the |
|
||||||
directed edge is ignored. |
|
||||||
|
|
||||||
This is the local-ratio algorithm for computing an approximate |
|
||||||
vertex cover. The algorithm greedily reduces the costs over edges, |
|
||||||
iteratively building a cover. The worst-case runtime of this |
|
||||||
implementation is $O(m \log n)$, where $n$ is the number |
|
||||||
of nodes and $m$ the number of edges in the graph. |
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] Bar-Yehuda, R., and Even, S. (1985). "A local-ratio theorem for |
|
||||||
approximating the weighted vertex cover problem." |
|
||||||
*Annals of Discrete Mathematics*, 25, 27–46 |
|
||||||
<http://www.cs.technion.ac.il/~reuven/PDF/vc_lr.pdf> |
|
||||||
|
|
||||||
""" |
|
||||||
cost = dict(G.nodes(data=weight, default=1)) |
|
||||||
# While there are uncovered edges, choose an uncovered and update |
|
||||||
# the cost of the remaining edges. |
|
||||||
for u, v in G.edges(): |
|
||||||
min_cost = min(cost[u], cost[v]) |
|
||||||
cost[u] -= min_cost |
|
||||||
cost[v] -= min_cost |
|
||||||
return {u for u, c in cost.items() if c == 0} |
|
@ -1,5 +0,0 @@ |
|||||||
from networkx.algorithms.assortativity.connectivity import * |
|
||||||
from networkx.algorithms.assortativity.correlation import * |
|
||||||
from networkx.algorithms.assortativity.mixing import * |
|
||||||
from networkx.algorithms.assortativity.neighbor_degree import * |
|
||||||
from networkx.algorithms.assortativity.pairs import * |
|
@ -1,128 +0,0 @@ |
|||||||
from collections import defaultdict |
|
||||||
|
|
||||||
__all__ = ["average_degree_connectivity", "k_nearest_neighbors"] |
|
||||||
|
|
||||||
|
|
||||||
def average_degree_connectivity( |
|
||||||
G, source="in+out", target="in+out", nodes=None, weight=None |
|
||||||
): |
|
||||||
r"""Compute the average degree connectivity of graph. |
|
||||||
|
|
||||||
The average degree connectivity is the average nearest neighbor degree of |
|
||||||
nodes with degree k. For weighted graphs, an analogous measure can |
|
||||||
be computed using the weighted average neighbors degree defined in |
|
||||||
[1]_, for a node `i`, as |
|
||||||
|
|
||||||
.. math:: |
|
||||||
|
|
||||||
k_{nn,i}^{w} = \frac{1}{s_i} \sum_{j \in N(i)} w_{ij} k_j |
|
||||||
|
|
||||||
where `s_i` is the weighted degree of node `i`, |
|
||||||
`w_{ij}` is the weight of the edge that links `i` and `j`, |
|
||||||
and `N(i)` are the neighbors of node `i`. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
|
|
||||||
source : "in"|"out"|"in+out" (default:"in+out") |
|
||||||
Directed graphs only. Use "in"- or "out"-degree for source node. |
|
||||||
|
|
||||||
target : "in"|"out"|"in+out" (default:"in+out" |
|
||||||
Directed graphs only. Use "in"- or "out"-degree for target node. |
|
||||||
|
|
||||||
nodes : list or iterable (optional) |
|
||||||
Compute neighbor connectivity for these nodes. The default is all |
|
||||||
nodes. |
|
||||||
|
|
||||||
weight : string or None, optional (default=None) |
|
||||||
The edge attribute that holds the numerical value used as a weight. |
|
||||||
If None, then each edge has weight 1. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
d : dict |
|
||||||
A dictionary keyed by degree k with the value of average connectivity. |
|
||||||
|
|
||||||
Raises |
|
||||||
------ |
|
||||||
ValueError |
|
||||||
If either `source` or `target` are not one of 'in', |
|
||||||
'out', or 'in+out'. |
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
>>> G = nx.path_graph(4) |
|
||||||
>>> G.edges[1, 2]["weight"] = 3 |
|
||||||
>>> nx.k_nearest_neighbors(G) |
|
||||||
{1: 2.0, 2: 1.5} |
|
||||||
>>> nx.k_nearest_neighbors(G, weight="weight") |
|
||||||
{1: 2.0, 2: 1.75} |
|
||||||
|
|
||||||
See also |
|
||||||
-------- |
|
||||||
neighbors_average_degree |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
This algorithm is sometimes called "k nearest neighbors" and is also |
|
||||||
available as `k_nearest_neighbors`. |
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] A. Barrat, M. Barthélemy, R. Pastor-Satorras, and A. Vespignani, |
|
||||||
"The architecture of complex weighted networks". |
|
||||||
PNAS 101 (11): 3747–3752 (2004). |
|
||||||
""" |
|
||||||
# First, determine the type of neighbors and the type of degree to use. |
|
||||||
if G.is_directed(): |
|
||||||
if source not in ("in", "out", "in+out"): |
|
||||||
raise ValueError('source must be one of "in", "out", or "in+out"') |
|
||||||
if target not in ("in", "out", "in+out"): |
|
||||||
raise ValueError('target must be one of "in", "out", or "in+out"') |
|
||||||
direction = {"out": G.out_degree, "in": G.in_degree, "in+out": G.degree} |
|
||||||
neighbor_funcs = { |
|
||||||
"out": G.successors, |
|
||||||
"in": G.predecessors, |
|
||||||
"in+out": G.neighbors, |
|
||||||
} |
|
||||||
source_degree = direction[source] |
|
||||||
target_degree = direction[target] |
|
||||||
neighbors = neighbor_funcs[source] |
|
||||||
# `reverse` indicates whether to look at the in-edge when |
|
||||||
# computing the weight of an edge. |
|
||||||
reverse = source == "in" |
|
||||||
else: |
|
||||||
source_degree = G.degree |
|
||||||
target_degree = G.degree |
|
||||||
neighbors = G.neighbors |
|
||||||
reverse = False |
|
||||||
dsum = defaultdict(int) |
|
||||||
dnorm = defaultdict(int) |
|
||||||
# Check if `source_nodes` is actually a single node in the graph. |
|
||||||
source_nodes = source_degree(nodes) |
|
||||||
if nodes in G: |
|
||||||
source_nodes = [(nodes, source_degree(nodes))] |
|
||||||
for n, k in source_nodes: |
|
||||||
nbrdeg = target_degree(neighbors(n)) |
|
||||||
if weight is None: |
|
||||||
s = sum(d for n, d in nbrdeg) |
|
||||||
else: # weight nbr degree by weight of (n,nbr) edge |
|
||||||
if reverse: |
|
||||||
s = sum(G[nbr][n].get(weight, 1) * d for nbr, d in nbrdeg) |
|
||||||
else: |
|
||||||
s = sum(G[n][nbr].get(weight, 1) * d for nbr, d in nbrdeg) |
|
||||||
dnorm[k] += source_degree(n, weight=weight) |
|
||||||
dsum[k] += s |
|
||||||
|
|
||||||
# normalize |
|
||||||
dc = {} |
|
||||||
for k, avg in dsum.items(): |
|
||||||
dc[k] = avg |
|
||||||
norm = dnorm[k] |
|
||||||
if avg > 0 and norm > 0: |
|
||||||
dc[k] /= norm |
|
||||||
return dc |
|
||||||
|
|
||||||
|
|
||||||
k_nearest_neighbors = average_degree_connectivity |
|
@ -1,287 +0,0 @@ |
|||||||
"""Node assortativity coefficients and correlation measures. |
|
||||||
""" |
|
||||||
from networkx.algorithms.assortativity.mixing import ( |
|
||||||
degree_mixing_matrix, |
|
||||||
attribute_mixing_matrix, |
|
||||||
numeric_mixing_matrix, |
|
||||||
) |
|
||||||
from networkx.algorithms.assortativity.pairs import node_degree_xy |
|
||||||
|
|
||||||
__all__ = [ |
|
||||||
"degree_pearson_correlation_coefficient", |
|
||||||
"degree_assortativity_coefficient", |
|
||||||
"attribute_assortativity_coefficient", |
|
||||||
"numeric_assortativity_coefficient", |
|
||||||
] |
|
||||||
|
|
||||||
|
|
||||||
def degree_assortativity_coefficient(G, x="out", y="in", weight=None, nodes=None): |
|
||||||
"""Compute degree assortativity of graph. |
|
||||||
|
|
||||||
Assortativity measures the similarity of connections |
|
||||||
in the graph with respect to the node degree. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
|
|
||||||
x: string ('in','out') |
|
||||||
The degree type for source node (directed graphs only). |
|
||||||
|
|
||||||
y: string ('in','out') |
|
||||||
The degree type for target node (directed graphs only). |
|
||||||
|
|
||||||
weight: string or None, optional (default=None) |
|
||||||
The edge attribute that holds the numerical value used |
|
||||||
as a weight. If None, then each edge has weight 1. |
|
||||||
The degree is the sum of the edge weights adjacent to the node. |
|
||||||
|
|
||||||
nodes: list or iterable (optional) |
|
||||||
Compute degree assortativity only for nodes in container. |
|
||||||
The default is all nodes. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
r : float |
|
||||||
Assortativity of graph by degree. |
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
>>> G = nx.path_graph(4) |
|
||||||
>>> r = nx.degree_assortativity_coefficient(G) |
|
||||||
>>> print(f"{r:3.1f}") |
|
||||||
-0.5 |
|
||||||
|
|
||||||
See Also |
|
||||||
-------- |
|
||||||
attribute_assortativity_coefficient |
|
||||||
numeric_assortativity_coefficient |
|
||||||
neighbor_connectivity |
|
||||||
degree_mixing_dict |
|
||||||
degree_mixing_matrix |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
This computes Eq. (21) in Ref. [1]_ , where e is the joint |
|
||||||
probability distribution (mixing matrix) of the degrees. If G is |
|
||||||
directed than the matrix e is the joint probability of the |
|
||||||
user-specified degree type for the source and target. |
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] M. E. J. Newman, Mixing patterns in networks, |
|
||||||
Physical Review E, 67 026126, 2003 |
|
||||||
.. [2] Foster, J.G., Foster, D.V., Grassberger, P. & Paczuski, M. |
|
||||||
Edge direction and the structure of networks, PNAS 107, 10815-20 (2010). |
|
||||||
""" |
|
||||||
M = degree_mixing_matrix(G, x=x, y=y, nodes=nodes, weight=weight) |
|
||||||
return numeric_ac(M) |
|
||||||
|
|
||||||
|
|
||||||
def degree_pearson_correlation_coefficient(G, x="out", y="in", weight=None, nodes=None): |
|
||||||
"""Compute degree assortativity of graph. |
|
||||||
|
|
||||||
Assortativity measures the similarity of connections |
|
||||||
in the graph with respect to the node degree. |
|
||||||
|
|
||||||
This is the same as degree_assortativity_coefficient but uses the |
|
||||||
potentially faster scipy.stats.pearsonr function. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
|
|
||||||
x: string ('in','out') |
|
||||||
The degree type for source node (directed graphs only). |
|
||||||
|
|
||||||
y: string ('in','out') |
|
||||||
The degree type for target node (directed graphs only). |
|
||||||
|
|
||||||
weight: string or None, optional (default=None) |
|
||||||
The edge attribute that holds the numerical value used |
|
||||||
as a weight. If None, then each edge has weight 1. |
|
||||||
The degree is the sum of the edge weights adjacent to the node. |
|
||||||
|
|
||||||
nodes: list or iterable (optional) |
|
||||||
Compute pearson correlation of degrees only for specified nodes. |
|
||||||
The default is all nodes. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
r : float |
|
||||||
Assortativity of graph by degree. |
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
>>> G = nx.path_graph(4) |
|
||||||
>>> r = nx.degree_pearson_correlation_coefficient(G) |
|
||||||
>>> print(f"{r:3.1f}") |
|
||||||
-0.5 |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
This calls scipy.stats.pearsonr. |
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] M. E. J. Newman, Mixing patterns in networks |
|
||||||
Physical Review E, 67 026126, 2003 |
|
||||||
.. [2] Foster, J.G., Foster, D.V., Grassberger, P. & Paczuski, M. |
|
||||||
Edge direction and the structure of networks, PNAS 107, 10815-20 (2010). |
|
||||||
""" |
|
||||||
try: |
|
||||||
import scipy.stats as stats |
|
||||||
except ImportError as e: |
|
||||||
raise ImportError("Assortativity requires SciPy:" "http://scipy.org/ ") from e |
|
||||||
xy = node_degree_xy(G, x=x, y=y, nodes=nodes, weight=weight) |
|
||||||
x, y = zip(*xy) |
|
||||||
return stats.pearsonr(x, y)[0] |
|
||||||
|
|
||||||
|
|
||||||
def attribute_assortativity_coefficient(G, attribute, nodes=None): |
|
||||||
"""Compute assortativity for node attributes. |
|
||||||
|
|
||||||
Assortativity measures the similarity of connections |
|
||||||
in the graph with respect to the given attribute. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
|
|
||||||
attribute : string |
|
||||||
Node attribute key |
|
||||||
|
|
||||||
nodes: list or iterable (optional) |
|
||||||
Compute attribute assortativity for nodes in container. |
|
||||||
The default is all nodes. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
r: float |
|
||||||
Assortativity of graph for given attribute |
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
>>> G = nx.Graph() |
|
||||||
>>> G.add_nodes_from([0, 1], color="red") |
|
||||||
>>> G.add_nodes_from([2, 3], color="blue") |
|
||||||
>>> G.add_edges_from([(0, 1), (2, 3)]) |
|
||||||
>>> print(nx.attribute_assortativity_coefficient(G, "color")) |
|
||||||
1.0 |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
This computes Eq. (2) in Ref. [1]_ , (trace(M)-sum(M^2))/(1-sum(M^2)), |
|
||||||
where M is the joint probability distribution (mixing matrix) |
|
||||||
of the specified attribute. |
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] M. E. J. Newman, Mixing patterns in networks, |
|
||||||
Physical Review E, 67 026126, 2003 |
|
||||||
""" |
|
||||||
M = attribute_mixing_matrix(G, attribute, nodes) |
|
||||||
return attribute_ac(M) |
|
||||||
|
|
||||||
|
|
||||||
def numeric_assortativity_coefficient(G, attribute, nodes=None): |
|
||||||
"""Compute assortativity for numerical node attributes. |
|
||||||
|
|
||||||
Assortativity measures the similarity of connections |
|
||||||
in the graph with respect to the given numeric attribute. |
|
||||||
The numeric attribute must be an integer. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
|
|
||||||
attribute : string |
|
||||||
Node attribute key. The corresponding attribute value must be an |
|
||||||
integer. |
|
||||||
|
|
||||||
nodes: list or iterable (optional) |
|
||||||
Compute numeric assortativity only for attributes of nodes in |
|
||||||
container. The default is all nodes. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
r: float |
|
||||||
Assortativity of graph for given attribute |
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
>>> G = nx.Graph() |
|
||||||
>>> G.add_nodes_from([0, 1], size=2) |
|
||||||
>>> G.add_nodes_from([2, 3], size=3) |
|
||||||
>>> G.add_edges_from([(0, 1), (2, 3)]) |
|
||||||
>>> print(nx.numeric_assortativity_coefficient(G, "size")) |
|
||||||
1.0 |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
This computes Eq. (21) in Ref. [1]_ , for the mixing matrix of |
|
||||||
of the specified attribute. |
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] M. E. J. Newman, Mixing patterns in networks |
|
||||||
Physical Review E, 67 026126, 2003 |
|
||||||
""" |
|
||||||
a = numeric_mixing_matrix(G, attribute, nodes) |
|
||||||
return numeric_ac(a) |
|
||||||
|
|
||||||
|
|
||||||
def attribute_ac(M): |
|
||||||
"""Compute assortativity for attribute matrix M. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
M : numpy.ndarray |
|
||||||
2D ndarray representing the attribute mixing matrix. |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
This computes Eq. (2) in Ref. [1]_ , (trace(e)-sum(e^2))/(1-sum(e^2)), |
|
||||||
where e is the joint probability distribution (mixing matrix) |
|
||||||
of the specified attribute. |
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] M. E. J. Newman, Mixing patterns in networks, |
|
||||||
Physical Review E, 67 026126, 2003 |
|
||||||
""" |
|
||||||
try: |
|
||||||
import numpy |
|
||||||
except ImportError as e: |
|
||||||
raise ImportError( |
|
||||||
"attribute_assortativity requires " "NumPy: http://scipy.org/" |
|
||||||
) from e |
|
||||||
if M.sum() != 1.0: |
|
||||||
M = M / M.sum() |
|
||||||
s = (M @ M).sum() |
|
||||||
t = M.trace() |
|
||||||
r = (t - s) / (1 - s) |
|
||||||
return r |
|
||||||
|
|
||||||
|
|
||||||
def numeric_ac(M): |
|
||||||
# M is a numpy matrix or array |
|
||||||
# numeric assortativity coefficient, pearsonr |
|
||||||
try: |
|
||||||
import numpy |
|
||||||
except ImportError as e: |
|
||||||
raise ImportError( |
|
||||||
"numeric_assortativity requires " "NumPy: http://scipy.org/" |
|
||||||
) from e |
|
||||||
if M.sum() != 1.0: |
|
||||||
M = M / float(M.sum()) |
|
||||||
nx, ny = M.shape # nx=ny |
|
||||||
x = numpy.arange(nx) |
|
||||||
y = numpy.arange(ny) |
|
||||||
a = M.sum(axis=0) |
|
||||||
b = M.sum(axis=1) |
|
||||||
vara = (a * x ** 2).sum() - ((a * x).sum()) ** 2 |
|
||||||
varb = (b * x ** 2).sum() - ((b * x).sum()) ** 2 |
|
||||||
xy = numpy.outer(x, y) |
|
||||||
ab = numpy.outer(a, b) |
|
||||||
return (xy * (M - ab)).sum() / numpy.sqrt(vara * varb) |
|
@ -1,233 +0,0 @@ |
|||||||
""" |
|
||||||
Mixing matrices for node attributes and degree. |
|
||||||
""" |
|
||||||
from networkx.utils import dict_to_numpy_array |
|
||||||
from networkx.algorithms.assortativity.pairs import node_degree_xy, node_attribute_xy |
|
||||||
|
|
||||||
__all__ = [ |
|
||||||
"attribute_mixing_matrix", |
|
||||||
"attribute_mixing_dict", |
|
||||||
"degree_mixing_matrix", |
|
||||||
"degree_mixing_dict", |
|
||||||
"numeric_mixing_matrix", |
|
||||||
"mixing_dict", |
|
||||||
] |
|
||||||
|
|
||||||
|
|
||||||
def attribute_mixing_dict(G, attribute, nodes=None, normalized=False): |
|
||||||
"""Returns dictionary representation of mixing matrix for attribute. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : graph |
|
||||||
NetworkX graph object. |
|
||||||
|
|
||||||
attribute : string |
|
||||||
Node attribute key. |
|
||||||
|
|
||||||
nodes: list or iterable (optional) |
|
||||||
Unse nodes in container to build the dict. The default is all nodes. |
|
||||||
|
|
||||||
normalized : bool (default=False) |
|
||||||
Return counts if False or probabilities if True. |
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
>>> G = nx.Graph() |
|
||||||
>>> G.add_nodes_from([0, 1], color="red") |
|
||||||
>>> G.add_nodes_from([2, 3], color="blue") |
|
||||||
>>> G.add_edge(1, 3) |
|
||||||
>>> d = nx.attribute_mixing_dict(G, "color") |
|
||||||
>>> print(d["red"]["blue"]) |
|
||||||
1 |
|
||||||
>>> print(d["blue"]["red"]) # d symmetric for undirected graphs |
|
||||||
1 |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
d : dictionary |
|
||||||
Counts or joint probability of occurrence of attribute pairs. |
|
||||||
""" |
|
||||||
xy_iter = node_attribute_xy(G, attribute, nodes) |
|
||||||
return mixing_dict(xy_iter, normalized=normalized) |
|
||||||
|
|
||||||
|
|
||||||
def attribute_mixing_matrix(G, attribute, nodes=None, mapping=None, normalized=True): |
|
||||||
"""Returns mixing matrix for attribute. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : graph |
|
||||||
NetworkX graph object. |
|
||||||
|
|
||||||
attribute : string |
|
||||||
Node attribute key. |
|
||||||
|
|
||||||
nodes: list or iterable (optional) |
|
||||||
Use only nodes in container to build the matrix. The default is |
|
||||||
all nodes. |
|
||||||
|
|
||||||
mapping : dictionary, optional |
|
||||||
Mapping from node attribute to integer index in matrix. |
|
||||||
If not specified, an arbitrary ordering will be used. |
|
||||||
|
|
||||||
normalized : bool (default=True) |
|
||||||
Return counts if False or probabilities if True. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
m: numpy array |
|
||||||
Counts or joint probability of occurrence of attribute pairs. |
|
||||||
""" |
|
||||||
d = attribute_mixing_dict(G, attribute, nodes) |
|
||||||
a = dict_to_numpy_array(d, mapping=mapping) |
|
||||||
if normalized: |
|
||||||
a = a / a.sum() |
|
||||||
return a |
|
||||||
|
|
||||||
|
|
||||||
def degree_mixing_dict(G, x="out", y="in", weight=None, nodes=None, normalized=False): |
|
||||||
"""Returns dictionary representation of mixing matrix for degree. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : graph |
|
||||||
NetworkX graph object. |
|
||||||
|
|
||||||
x: string ('in','out') |
|
||||||
The degree type for source node (directed graphs only). |
|
||||||
|
|
||||||
y: string ('in','out') |
|
||||||
The degree type for target node (directed graphs only). |
|
||||||
|
|
||||||
weight: string or None, optional (default=None) |
|
||||||
The edge attribute that holds the numerical value used |
|
||||||
as a weight. If None, then each edge has weight 1. |
|
||||||
The degree is the sum of the edge weights adjacent to the node. |
|
||||||
|
|
||||||
normalized : bool (default=False) |
|
||||||
Return counts if False or probabilities if True. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
d: dictionary |
|
||||||
Counts or joint probability of occurrence of degree pairs. |
|
||||||
""" |
|
||||||
xy_iter = node_degree_xy(G, x=x, y=y, nodes=nodes, weight=weight) |
|
||||||
return mixing_dict(xy_iter, normalized=normalized) |
|
||||||
|
|
||||||
|
|
||||||
def degree_mixing_matrix(G, x="out", y="in", weight=None, nodes=None, normalized=True): |
|
||||||
"""Returns mixing matrix for attribute. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : graph |
|
||||||
NetworkX graph object. |
|
||||||
|
|
||||||
x: string ('in','out') |
|
||||||
The degree type for source node (directed graphs only). |
|
||||||
|
|
||||||
y: string ('in','out') |
|
||||||
The degree type for target node (directed graphs only). |
|
||||||
|
|
||||||
nodes: list or iterable (optional) |
|
||||||
Build the matrix using only nodes in container. |
|
||||||
The default is all nodes. |
|
||||||
|
|
||||||
weight: string or None, optional (default=None) |
|
||||||
The edge attribute that holds the numerical value used |
|
||||||
as a weight. If None, then each edge has weight 1. |
|
||||||
The degree is the sum of the edge weights adjacent to the node. |
|
||||||
|
|
||||||
normalized : bool (default=True) |
|
||||||
Return counts if False or probabilities if True. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
m: numpy array |
|
||||||
Counts, or joint probability, of occurrence of node degree. |
|
||||||
""" |
|
||||||
d = degree_mixing_dict(G, x=x, y=y, nodes=nodes, weight=weight) |
|
||||||
s = set(d.keys()) |
|
||||||
for k, v in d.items(): |
|
||||||
s.update(v.keys()) |
|
||||||
m = max(s) |
|
||||||
mapping = {x: x for x in range(m + 1)} |
|
||||||
a = dict_to_numpy_array(d, mapping=mapping) |
|
||||||
if normalized: |
|
||||||
a = a / a.sum() |
|
||||||
return a |
|
||||||
|
|
||||||
|
|
||||||
def numeric_mixing_matrix(G, attribute, nodes=None, normalized=True): |
|
||||||
"""Returns numeric mixing matrix for attribute. |
|
||||||
|
|
||||||
The attribute must be an integer. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : graph |
|
||||||
NetworkX graph object. |
|
||||||
|
|
||||||
attribute : string |
|
||||||
Node attribute key. The corresponding attribute must be an integer. |
|
||||||
|
|
||||||
nodes: list or iterable (optional) |
|
||||||
Build the matrix only with nodes in container. The default is all nodes. |
|
||||||
|
|
||||||
normalized : bool (default=True) |
|
||||||
Return counts if False or probabilities if True. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
m: numpy array |
|
||||||
Counts, or joint, probability of occurrence of node attribute pairs. |
|
||||||
""" |
|
||||||
d = attribute_mixing_dict(G, attribute, nodes) |
|
||||||
s = set(d.keys()) |
|
||||||
for k, v in d.items(): |
|
||||||
s.update(v.keys()) |
|
||||||
m = max(s) |
|
||||||
mapping = {x: x for x in range(m + 1)} |
|
||||||
a = dict_to_numpy_array(d, mapping=mapping) |
|
||||||
if normalized: |
|
||||||
a = a / a.sum() |
|
||||||
return a |
|
||||||
|
|
||||||
|
|
||||||
def mixing_dict(xy, normalized=False): |
|
||||||
"""Returns a dictionary representation of mixing matrix. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
xy : list or container of two-tuples |
|
||||||
Pairs of (x,y) items. |
|
||||||
|
|
||||||
attribute : string |
|
||||||
Node attribute key |
|
||||||
|
|
||||||
normalized : bool (default=False) |
|
||||||
Return counts if False or probabilities if True. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
d: dictionary |
|
||||||
Counts or Joint probability of occurrence of values in xy. |
|
||||||
""" |
|
||||||
d = {} |
|
||||||
psum = 0.0 |
|
||||||
for x, y in xy: |
|
||||||
if x not in d: |
|
||||||
d[x] = {} |
|
||||||
if y not in d: |
|
||||||
d[y] = {} |
|
||||||
v = d[x].get(y, 0) |
|
||||||
d[x][y] = v + 1 |
|
||||||
psum += 1 |
|
||||||
|
|
||||||
if normalized: |
|
||||||
for k, jdict in d.items(): |
|
||||||
for j in jdict: |
|
||||||
jdict[j] /= psum |
|
||||||
return d |
|
@ -1,122 +0,0 @@ |
|||||||
__all__ = ["average_neighbor_degree"] |
|
||||||
|
|
||||||
|
|
||||||
def _average_nbr_deg(G, source_degree, target_degree, nodes=None, weight=None): |
|
||||||
# average degree of neighbors |
|
||||||
avg = {} |
|
||||||
for n, deg in source_degree(nodes, weight=weight): |
|
||||||
# normalize but not by zero degree |
|
||||||
if deg == 0: |
|
||||||
deg = 1 |
|
||||||
nbrdeg = target_degree(G[n]) |
|
||||||
if weight is None: |
|
||||||
avg[n] = sum(d for n, d in nbrdeg) / float(deg) |
|
||||||
else: |
|
||||||
avg[n] = sum((G[n][nbr].get(weight, 1) * d for nbr, d in nbrdeg)) / float( |
|
||||||
deg |
|
||||||
) |
|
||||||
return avg |
|
||||||
|
|
||||||
|
|
||||||
def average_neighbor_degree(G, source="out", target="out", nodes=None, weight=None): |
|
||||||
r"""Returns the average degree of the neighborhood of each node. |
|
||||||
|
|
||||||
The average neighborhood degree of a node `i` is |
|
||||||
|
|
||||||
.. math:: |
|
||||||
|
|
||||||
k_{nn,i} = \frac{1}{|N(i)|} \sum_{j \in N(i)} k_j |
|
||||||
|
|
||||||
where `N(i)` are the neighbors of node `i` and `k_j` is |
|
||||||
the degree of node `j` which belongs to `N(i)`. For weighted |
|
||||||
graphs, an analogous measure can be defined [1]_, |
|
||||||
|
|
||||||
.. math:: |
|
||||||
|
|
||||||
k_{nn,i}^{w} = \frac{1}{s_i} \sum_{j \in N(i)} w_{ij} k_j |
|
||||||
|
|
||||||
where `s_i` is the weighted degree of node `i`, `w_{ij}` |
|
||||||
is the weight of the edge that links `i` and `j` and |
|
||||||
`N(i)` are the neighbors of node `i`. |
|
||||||
|
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
|
|
||||||
source : string ("in"|"out") |
|
||||||
Directed graphs only. |
|
||||||
Use "in"- or "out"-degree for source node. |
|
||||||
|
|
||||||
target : string ("in"|"out") |
|
||||||
Directed graphs only. |
|
||||||
Use "in"- or "out"-degree for target node. |
|
||||||
|
|
||||||
nodes : list or iterable, optional |
|
||||||
Compute neighbor degree for specified nodes. The default is |
|
||||||
all nodes in the graph. |
|
||||||
|
|
||||||
weight : string or None, optional (default=None) |
|
||||||
The edge attribute that holds the numerical value used as a weight. |
|
||||||
If None, then each edge has weight 1. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
d: dict |
|
||||||
A dictionary keyed by node with average neighbors degree value. |
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
>>> G = nx.path_graph(4) |
|
||||||
>>> G.edges[0, 1]["weight"] = 5 |
|
||||||
>>> G.edges[2, 3]["weight"] = 3 |
|
||||||
|
|
||||||
>>> nx.average_neighbor_degree(G) |
|
||||||
{0: 2.0, 1: 1.5, 2: 1.5, 3: 2.0} |
|
||||||
>>> nx.average_neighbor_degree(G, weight="weight") |
|
||||||
{0: 2.0, 1: 1.1666666666666667, 2: 1.25, 3: 2.0} |
|
||||||
|
|
||||||
>>> G = nx.DiGraph() |
|
||||||
>>> nx.add_path(G, [0, 1, 2, 3]) |
|
||||||
>>> nx.average_neighbor_degree(G, source="in", target="in") |
|
||||||
{0: 1.0, 1: 1.0, 2: 1.0, 3: 0.0} |
|
||||||
|
|
||||||
>>> nx.average_neighbor_degree(G, source="out", target="out") |
|
||||||
{0: 1.0, 1: 1.0, 2: 0.0, 3: 0.0} |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
For directed graphs you can also specify in-degree or out-degree |
|
||||||
by passing keyword arguments. |
|
||||||
|
|
||||||
See Also |
|
||||||
-------- |
|
||||||
average_degree_connectivity |
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] A. Barrat, M. Barthélemy, R. Pastor-Satorras, and A. Vespignani, |
|
||||||
"The architecture of complex weighted networks". |
|
||||||
PNAS 101 (11): 3747–3752 (2004). |
|
||||||
""" |
|
||||||
source_degree = G.degree |
|
||||||
target_degree = G.degree |
|
||||||
if G.is_directed(): |
|
||||||
direction = {"out": G.out_degree, "in": G.in_degree} |
|
||||||
source_degree = direction[source] |
|
||||||
target_degree = direction[target] |
|
||||||
return _average_nbr_deg(G, source_degree, target_degree, nodes=nodes, weight=weight) |
|
||||||
|
|
||||||
|
|
||||||
# obsolete |
|
||||||
# def average_neighbor_in_degree(G, nodes=None, weight=None): |
|
||||||
# if not G.is_directed(): |
|
||||||
# raise nx.NetworkXError("Not defined for undirected graphs.") |
|
||||||
# return _average_nbr_deg(G, G.in_degree, G.in_degree, nodes, weight) |
|
||||||
# average_neighbor_in_degree.__doc__=average_neighbor_degree.__doc__ |
|
||||||
|
|
||||||
# def average_neighbor_out_degree(G, nodes=None, weight=None): |
|
||||||
# if not G.is_directed(): |
|
||||||
# raise nx.NetworkXError("Not defined for undirected graphs.") |
|
||||||
# return _average_nbr_deg(G, G.out_degree, G.out_degree, nodes, weight) |
|
||||||
# average_neighbor_out_degree.__doc__=average_neighbor_degree.__doc__ |
|
@ -1,116 +0,0 @@ |
|||||||
"""Generators of x-y pairs of node data.""" |
|
||||||
__all__ = ["node_attribute_xy", "node_degree_xy"] |
|
||||||
|
|
||||||
|
|
||||||
def node_attribute_xy(G, attribute, nodes=None): |
|
||||||
"""Returns iterator of node-attribute pairs for all edges in G. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G: NetworkX graph |
|
||||||
|
|
||||||
attribute: key |
|
||||||
The node attribute key. |
|
||||||
|
|
||||||
nodes: list or iterable (optional) |
|
||||||
Use only edges that are adjacency to specified nodes. |
|
||||||
The default is all nodes. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
(x,y): 2-tuple |
|
||||||
Generates 2-tuple of (attribute,attribute) values. |
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
>>> G = nx.DiGraph() |
|
||||||
>>> G.add_node(1, color="red") |
|
||||||
>>> G.add_node(2, color="blue") |
|
||||||
>>> G.add_edge(1, 2) |
|
||||||
>>> list(nx.node_attribute_xy(G, "color")) |
|
||||||
[('red', 'blue')] |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
For undirected graphs each edge is produced twice, once for each edge |
|
||||||
representation (u,v) and (v,u), with the exception of self-loop edges |
|
||||||
which only appear once. |
|
||||||
""" |
|
||||||
if nodes is None: |
|
||||||
nodes = set(G) |
|
||||||
else: |
|
||||||
nodes = set(nodes) |
|
||||||
Gnodes = G.nodes |
|
||||||
for u, nbrsdict in G.adjacency(): |
|
||||||
if u not in nodes: |
|
||||||
continue |
|
||||||
uattr = Gnodes[u].get(attribute, None) |
|
||||||
if G.is_multigraph(): |
|
||||||
for v, keys in nbrsdict.items(): |
|
||||||
vattr = Gnodes[v].get(attribute, None) |
|
||||||
for k, d in keys.items(): |
|
||||||
yield (uattr, vattr) |
|
||||||
else: |
|
||||||
for v, eattr in nbrsdict.items(): |
|
||||||
vattr = Gnodes[v].get(attribute, None) |
|
||||||
yield (uattr, vattr) |
|
||||||
|
|
||||||
|
|
||||||
def node_degree_xy(G, x="out", y="in", weight=None, nodes=None): |
|
||||||
"""Generate node degree-degree pairs for edges in G. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G: NetworkX graph |
|
||||||
|
|
||||||
x: string ('in','out') |
|
||||||
The degree type for source node (directed graphs only). |
|
||||||
|
|
||||||
y: string ('in','out') |
|
||||||
The degree type for target node (directed graphs only). |
|
||||||
|
|
||||||
weight: string or None, optional (default=None) |
|
||||||
The edge attribute that holds the numerical value used |
|
||||||
as a weight. If None, then each edge has weight 1. |
|
||||||
The degree is the sum of the edge weights adjacent to the node. |
|
||||||
|
|
||||||
nodes: list or iterable (optional) |
|
||||||
Use only edges that are adjacency to specified nodes. |
|
||||||
The default is all nodes. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
(x,y): 2-tuple |
|
||||||
Generates 2-tuple of (degree,degree) values. |
|
||||||
|
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
>>> G = nx.DiGraph() |
|
||||||
>>> G.add_edge(1, 2) |
|
||||||
>>> list(nx.node_degree_xy(G, x="out", y="in")) |
|
||||||
[(1, 1)] |
|
||||||
>>> list(nx.node_degree_xy(G, x="in", y="out")) |
|
||||||
[(0, 0)] |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
For undirected graphs each edge is produced twice, once for each edge |
|
||||||
representation (u,v) and (v,u), with the exception of self-loop edges |
|
||||||
which only appear once. |
|
||||||
""" |
|
||||||
if nodes is None: |
|
||||||
nodes = set(G) |
|
||||||
else: |
|
||||||
nodes = set(nodes) |
|
||||||
xdeg = G.degree |
|
||||||
ydeg = G.degree |
|
||||||
if G.is_directed(): |
|
||||||
direction = {"out": G.out_degree, "in": G.in_degree} |
|
||||||
xdeg = direction[x] |
|
||||||
ydeg = direction[y] |
|
||||||
|
|
||||||
for u, degu in xdeg(nodes, weight=weight): |
|
||||||
neighbors = (nbr for _, nbr in G.edges(u) if nbr in nodes) |
|
||||||
for v, degv in ydeg(neighbors, weight=weight): |
|
||||||
yield degu, degv |
|
@ -1,51 +0,0 @@ |
|||||||
import networkx as nx |
|
||||||
|
|
||||||
|
|
||||||
class BaseTestAttributeMixing: |
|
||||||
@classmethod |
|
||||||
def setup_class(cls): |
|
||||||
G = nx.Graph() |
|
||||||
G.add_nodes_from([0, 1], fish="one") |
|
||||||
G.add_nodes_from([2, 3], fish="two") |
|
||||||
G.add_nodes_from([4], fish="red") |
|
||||||
G.add_nodes_from([5], fish="blue") |
|
||||||
G.add_edges_from([(0, 1), (2, 3), (0, 4), (2, 5)]) |
|
||||||
cls.G = G |
|
||||||
|
|
||||||
D = nx.DiGraph() |
|
||||||
D.add_nodes_from([0, 1], fish="one") |
|
||||||
D.add_nodes_from([2, 3], fish="two") |
|
||||||
D.add_nodes_from([4], fish="red") |
|
||||||
D.add_nodes_from([5], fish="blue") |
|
||||||
D.add_edges_from([(0, 1), (2, 3), (0, 4), (2, 5)]) |
|
||||||
cls.D = D |
|
||||||
|
|
||||||
M = nx.MultiGraph() |
|
||||||
M.add_nodes_from([0, 1], fish="one") |
|
||||||
M.add_nodes_from([2, 3], fish="two") |
|
||||||
M.add_nodes_from([4], fish="red") |
|
||||||
M.add_nodes_from([5], fish="blue") |
|
||||||
M.add_edges_from([(0, 1), (0, 1), (2, 3)]) |
|
||||||
cls.M = M |
|
||||||
|
|
||||||
S = nx.Graph() |
|
||||||
S.add_nodes_from([0, 1], fish="one") |
|
||||||
S.add_nodes_from([2, 3], fish="two") |
|
||||||
S.add_nodes_from([4], fish="red") |
|
||||||
S.add_nodes_from([5], fish="blue") |
|
||||||
S.add_edge(0, 0) |
|
||||||
S.add_edge(2, 2) |
|
||||||
cls.S = S |
|
||||||
|
|
||||||
|
|
||||||
class BaseTestDegreeMixing: |
|
||||||
@classmethod |
|
||||||
def setup_class(cls): |
|
||||||
cls.P4 = nx.path_graph(4) |
|
||||||
cls.D = nx.DiGraph() |
|
||||||
cls.D.add_edges_from([(0, 2), (0, 3), (1, 3), (2, 3)]) |
|
||||||
cls.M = nx.MultiGraph() |
|
||||||
nx.add_path(cls.M, range(4)) |
|
||||||
cls.M.add_edge(0, 1) |
|
||||||
cls.S = nx.Graph() |
|
||||||
cls.S.add_edges_from([(0, 0), (1, 1)]) |
|
@ -1,139 +0,0 @@ |
|||||||
from itertools import permutations |
|
||||||
|
|
||||||
import pytest |
|
||||||
|
|
||||||
import networkx as nx |
|
||||||
from networkx.testing import almost_equal |
|
||||||
|
|
||||||
|
|
||||||
class TestNeighborConnectivity: |
|
||||||
def test_degree_p4(self): |
|
||||||
G = nx.path_graph(4) |
|
||||||
answer = {1: 2.0, 2: 1.5} |
|
||||||
nd = nx.average_degree_connectivity(G) |
|
||||||
assert nd == answer |
|
||||||
|
|
||||||
D = G.to_directed() |
|
||||||
answer = {2: 2.0, 4: 1.5} |
|
||||||
nd = nx.average_degree_connectivity(D) |
|
||||||
assert nd == answer |
|
||||||
|
|
||||||
answer = {1: 2.0, 2: 1.5} |
|
||||||
D = G.to_directed() |
|
||||||
nd = nx.average_degree_connectivity(D, source="in", target="in") |
|
||||||
assert nd == answer |
|
||||||
|
|
||||||
D = G.to_directed() |
|
||||||
nd = nx.average_degree_connectivity(D, source="in", target="in") |
|
||||||
assert nd == answer |
|
||||||
|
|
||||||
def test_degree_p4_weighted(self): |
|
||||||
G = nx.path_graph(4) |
|
||||||
G[1][2]["weight"] = 4 |
|
||||||
answer = {1: 2.0, 2: 1.8} |
|
||||||
nd = nx.average_degree_connectivity(G, weight="weight") |
|
||||||
assert nd == answer |
|
||||||
answer = {1: 2.0, 2: 1.5} |
|
||||||
nd = nx.average_degree_connectivity(G) |
|
||||||
assert nd == answer |
|
||||||
|
|
||||||
D = G.to_directed() |
|
||||||
answer = {2: 2.0, 4: 1.8} |
|
||||||
nd = nx.average_degree_connectivity(D, weight="weight") |
|
||||||
assert nd == answer |
|
||||||
|
|
||||||
answer = {1: 2.0, 2: 1.8} |
|
||||||
D = G.to_directed() |
|
||||||
nd = nx.average_degree_connectivity( |
|
||||||
D, weight="weight", source="in", target="in" |
|
||||||
) |
|
||||||
assert nd == answer |
|
||||||
|
|
||||||
D = G.to_directed() |
|
||||||
nd = nx.average_degree_connectivity( |
|
||||||
D, source="in", target="out", weight="weight" |
|
||||||
) |
|
||||||
assert nd == answer |
|
||||||
|
|
||||||
def test_weight_keyword(self): |
|
||||||
G = nx.path_graph(4) |
|
||||||
G[1][2]["other"] = 4 |
|
||||||
answer = {1: 2.0, 2: 1.8} |
|
||||||
nd = nx.average_degree_connectivity(G, weight="other") |
|
||||||
assert nd == answer |
|
||||||
answer = {1: 2.0, 2: 1.5} |
|
||||||
nd = nx.average_degree_connectivity(G, weight=None) |
|
||||||
assert nd == answer |
|
||||||
|
|
||||||
D = G.to_directed() |
|
||||||
answer = {2: 2.0, 4: 1.8} |
|
||||||
nd = nx.average_degree_connectivity(D, weight="other") |
|
||||||
assert nd == answer |
|
||||||
|
|
||||||
answer = {1: 2.0, 2: 1.8} |
|
||||||
D = G.to_directed() |
|
||||||
nd = nx.average_degree_connectivity(D, weight="other", source="in", target="in") |
|
||||||
assert nd == answer |
|
||||||
|
|
||||||
D = G.to_directed() |
|
||||||
nd = nx.average_degree_connectivity(D, weight="other", source="in", target="in") |
|
||||||
assert nd == answer |
|
||||||
|
|
||||||
def test_degree_barrat(self): |
|
||||||
G = nx.star_graph(5) |
|
||||||
G.add_edges_from([(5, 6), (5, 7), (5, 8), (5, 9)]) |
|
||||||
G[0][5]["weight"] = 5 |
|
||||||
nd = nx.average_degree_connectivity(G)[5] |
|
||||||
assert nd == 1.8 |
|
||||||
nd = nx.average_degree_connectivity(G, weight="weight")[5] |
|
||||||
assert almost_equal(nd, 3.222222, places=5) |
|
||||||
nd = nx.k_nearest_neighbors(G, weight="weight")[5] |
|
||||||
assert almost_equal(nd, 3.222222, places=5) |
|
||||||
|
|
||||||
def test_zero_deg(self): |
|
||||||
G = nx.DiGraph() |
|
||||||
G.add_edge(1, 2) |
|
||||||
G.add_edge(1, 3) |
|
||||||
G.add_edge(1, 4) |
|
||||||
c = nx.average_degree_connectivity(G) |
|
||||||
assert c == {1: 0, 3: 1} |
|
||||||
c = nx.average_degree_connectivity(G, source="in", target="in") |
|
||||||
assert c == {0: 0, 1: 0} |
|
||||||
c = nx.average_degree_connectivity(G, source="in", target="out") |
|
||||||
assert c == {0: 0, 1: 3} |
|
||||||
c = nx.average_degree_connectivity(G, source="in", target="in+out") |
|
||||||
assert c == {0: 0, 1: 3} |
|
||||||
c = nx.average_degree_connectivity(G, source="out", target="out") |
|
||||||
assert c == {0: 0, 3: 0} |
|
||||||
c = nx.average_degree_connectivity(G, source="out", target="in") |
|
||||||
assert c == {0: 0, 3: 1} |
|
||||||
c = nx.average_degree_connectivity(G, source="out", target="in+out") |
|
||||||
assert c == {0: 0, 3: 1} |
|
||||||
|
|
||||||
def test_in_out_weight(self): |
|
||||||
G = nx.DiGraph() |
|
||||||
G.add_edge(1, 2, weight=1) |
|
||||||
G.add_edge(1, 3, weight=1) |
|
||||||
G.add_edge(3, 1, weight=1) |
|
||||||
for s, t in permutations(["in", "out", "in+out"], 2): |
|
||||||
c = nx.average_degree_connectivity(G, source=s, target=t) |
|
||||||
cw = nx.average_degree_connectivity(G, source=s, target=t, weight="weight") |
|
||||||
assert c == cw |
|
||||||
|
|
||||||
def test_invalid_source(self): |
|
||||||
with pytest.raises(ValueError): |
|
||||||
G = nx.DiGraph() |
|
||||||
nx.average_degree_connectivity(G, source="bogus") |
|
||||||
|
|
||||||
def test_invalid_target(self): |
|
||||||
with pytest.raises(ValueError): |
|
||||||
G = nx.DiGraph() |
|
||||||
nx.average_degree_connectivity(G, target="bogus") |
|
||||||
|
|
||||||
def test_single_node(self): |
|
||||||
# TODO Is this really the intended behavior for providing a |
|
||||||
# single node as the argument `nodes`? Shouldn't the function |
|
||||||
# just return the connectivity value itself? |
|
||||||
G = nx.trivial_graph() |
|
||||||
conn = nx.average_degree_connectivity(G, nodes=0) |
|
||||||
assert conn == {0: 0} |
|
@ -1,76 +0,0 @@ |
|||||||
import pytest |
|
||||||
|
|
||||||
np = pytest.importorskip("numpy") |
|
||||||
npt = pytest.importorskip("numpy.testing") |
|
||||||
scipy = pytest.importorskip("scipy") |
|
||||||
|
|
||||||
|
|
||||||
import networkx as nx |
|
||||||
from .base_test import BaseTestAttributeMixing, BaseTestDegreeMixing |
|
||||||
from networkx.algorithms.assortativity.correlation import attribute_ac |
|
||||||
|
|
||||||
|
|
||||||
class TestDegreeMixingCorrelation(BaseTestDegreeMixing): |
|
||||||
def test_degree_assortativity_undirected(self): |
|
||||||
r = nx.degree_assortativity_coefficient(self.P4) |
|
||||||
npt.assert_almost_equal(r, -1.0 / 2, decimal=4) |
|
||||||
|
|
||||||
def test_degree_assortativity_directed(self): |
|
||||||
r = nx.degree_assortativity_coefficient(self.D) |
|
||||||
npt.assert_almost_equal(r, -0.57735, decimal=4) |
|
||||||
|
|
||||||
def test_degree_assortativity_multigraph(self): |
|
||||||
r = nx.degree_assortativity_coefficient(self.M) |
|
||||||
npt.assert_almost_equal(r, -1.0 / 7.0, decimal=4) |
|
||||||
|
|
||||||
def test_degree_pearson_assortativity_undirected(self): |
|
||||||
r = nx.degree_pearson_correlation_coefficient(self.P4) |
|
||||||
npt.assert_almost_equal(r, -1.0 / 2, decimal=4) |
|
||||||
|
|
||||||
def test_degree_pearson_assortativity_directed(self): |
|
||||||
r = nx.degree_pearson_correlation_coefficient(self.D) |
|
||||||
npt.assert_almost_equal(r, -0.57735, decimal=4) |
|
||||||
|
|
||||||
def test_degree_pearson_assortativity_multigraph(self): |
|
||||||
r = nx.degree_pearson_correlation_coefficient(self.M) |
|
||||||
npt.assert_almost_equal(r, -1.0 / 7.0, decimal=4) |
|
||||||
|
|
||||||
|
|
||||||
class TestAttributeMixingCorrelation(BaseTestAttributeMixing): |
|
||||||
def test_attribute_assortativity_undirected(self): |
|
||||||
r = nx.attribute_assortativity_coefficient(self.G, "fish") |
|
||||||
assert r == 6.0 / 22.0 |
|
||||||
|
|
||||||
def test_attribute_assortativity_directed(self): |
|
||||||
r = nx.attribute_assortativity_coefficient(self.D, "fish") |
|
||||||
assert r == 1.0 / 3.0 |
|
||||||
|
|
||||||
def test_attribute_assortativity_multigraph(self): |
|
||||||
r = nx.attribute_assortativity_coefficient(self.M, "fish") |
|
||||||
assert r == 1.0 |
|
||||||
|
|
||||||
def test_attribute_assortativity_coefficient(self): |
|
||||||
# from "Mixing patterns in networks" |
|
||||||
# fmt: off |
|
||||||
a = np.array([[0.258, 0.016, 0.035, 0.013], |
|
||||||
[0.012, 0.157, 0.058, 0.019], |
|
||||||
[0.013, 0.023, 0.306, 0.035], |
|
||||||
[0.005, 0.007, 0.024, 0.016]]) |
|
||||||
# fmt: on |
|
||||||
r = attribute_ac(a) |
|
||||||
npt.assert_almost_equal(r, 0.623, decimal=3) |
|
||||||
|
|
||||||
def test_attribute_assortativity_coefficient2(self): |
|
||||||
# fmt: off |
|
||||||
a = np.array([[0.18, 0.02, 0.01, 0.03], |
|
||||||
[0.02, 0.20, 0.03, 0.02], |
|
||||||
[0.01, 0.03, 0.16, 0.01], |
|
||||||
[0.03, 0.02, 0.01, 0.22]]) |
|
||||||
# fmt: on |
|
||||||
r = attribute_ac(a) |
|
||||||
npt.assert_almost_equal(r, 0.68, decimal=2) |
|
||||||
|
|
||||||
def test_attribute_assortativity(self): |
|
||||||
a = np.array([[50, 50, 0], [50, 50, 0], [0, 0, 2]]) |
|
||||||
r = attribute_ac(a) |
|
||||||
npt.assert_almost_equal(r, 0.029, decimal=3) |
|
@ -1,142 +0,0 @@ |
|||||||
import pytest |
|
||||||
|
|
||||||
np = pytest.importorskip("numpy") |
|
||||||
npt = pytest.importorskip("numpy.testing") |
|
||||||
|
|
||||||
|
|
||||||
import networkx as nx |
|
||||||
from .base_test import BaseTestAttributeMixing, BaseTestDegreeMixing |
|
||||||
|
|
||||||
|
|
||||||
class TestDegreeMixingDict(BaseTestDegreeMixing): |
|
||||||
def test_degree_mixing_dict_undirected(self): |
|
||||||
d = nx.degree_mixing_dict(self.P4) |
|
||||||
d_result = {1: {2: 2}, 2: {1: 2, 2: 2}} |
|
||||||
assert d == d_result |
|
||||||
|
|
||||||
def test_degree_mixing_dict_undirected_normalized(self): |
|
||||||
d = nx.degree_mixing_dict(self.P4, normalized=True) |
|
||||||
d_result = {1: {2: 1.0 / 3}, 2: {1: 1.0 / 3, 2: 1.0 / 3}} |
|
||||||
assert d == d_result |
|
||||||
|
|
||||||
def test_degree_mixing_dict_directed(self): |
|
||||||
d = nx.degree_mixing_dict(self.D) |
|
||||||
print(d) |
|
||||||
d_result = {1: {3: 2}, 2: {1: 1, 3: 1}, 3: {}} |
|
||||||
assert d == d_result |
|
||||||
|
|
||||||
def test_degree_mixing_dict_multigraph(self): |
|
||||||
d = nx.degree_mixing_dict(self.M) |
|
||||||
d_result = {1: {2: 1}, 2: {1: 1, 3: 3}, 3: {2: 3}} |
|
||||||
assert d == d_result |
|
||||||
|
|
||||||
|
|
||||||
class TestDegreeMixingMatrix(BaseTestDegreeMixing): |
|
||||||
def test_degree_mixing_matrix_undirected(self): |
|
||||||
# fmt: off |
|
||||||
a_result = np.array([[0, 0, 0], |
|
||||||
[0, 0, 2], |
|
||||||
[0, 2, 2]] |
|
||||||
) |
|
||||||
# fmt: on |
|
||||||
a = nx.degree_mixing_matrix(self.P4, normalized=False) |
|
||||||
npt.assert_equal(a, a_result) |
|
||||||
a = nx.degree_mixing_matrix(self.P4) |
|
||||||
npt.assert_equal(a, a_result / float(a_result.sum())) |
|
||||||
|
|
||||||
def test_degree_mixing_matrix_directed(self): |
|
||||||
# fmt: off |
|
||||||
a_result = np.array([[0, 0, 0, 0], |
|
||||||
[0, 0, 0, 2], |
|
||||||
[0, 1, 0, 1], |
|
||||||
[0, 0, 0, 0]] |
|
||||||
) |
|
||||||
# fmt: on |
|
||||||
a = nx.degree_mixing_matrix(self.D, normalized=False) |
|
||||||
npt.assert_equal(a, a_result) |
|
||||||
a = nx.degree_mixing_matrix(self.D) |
|
||||||
npt.assert_equal(a, a_result / float(a_result.sum())) |
|
||||||
|
|
||||||
def test_degree_mixing_matrix_multigraph(self): |
|
||||||
# fmt: off |
|
||||||
a_result = np.array([[0, 0, 0, 0], |
|
||||||
[0, 0, 1, 0], |
|
||||||
[0, 1, 0, 3], |
|
||||||
[0, 0, 3, 0]] |
|
||||||
) |
|
||||||
# fmt: on |
|
||||||
a = nx.degree_mixing_matrix(self.M, normalized=False) |
|
||||||
npt.assert_equal(a, a_result) |
|
||||||
a = nx.degree_mixing_matrix(self.M) |
|
||||||
npt.assert_equal(a, a_result / float(a_result.sum())) |
|
||||||
|
|
||||||
def test_degree_mixing_matrix_selfloop(self): |
|
||||||
# fmt: off |
|
||||||
a_result = np.array([[0, 0, 0], |
|
||||||
[0, 0, 0], |
|
||||||
[0, 0, 2]] |
|
||||||
) |
|
||||||
# fmt: on |
|
||||||
a = nx.degree_mixing_matrix(self.S, normalized=False) |
|
||||||
npt.assert_equal(a, a_result) |
|
||||||
a = nx.degree_mixing_matrix(self.S) |
|
||||||
npt.assert_equal(a, a_result / float(a_result.sum())) |
|
||||||
|
|
||||||
|
|
||||||
class TestAttributeMixingDict(BaseTestAttributeMixing): |
|
||||||
def test_attribute_mixing_dict_undirected(self): |
|
||||||
d = nx.attribute_mixing_dict(self.G, "fish") |
|
||||||
d_result = { |
|
||||||
"one": {"one": 2, "red": 1}, |
|
||||||
"two": {"two": 2, "blue": 1}, |
|
||||||
"red": {"one": 1}, |
|
||||||
"blue": {"two": 1}, |
|
||||||
} |
|
||||||
assert d == d_result |
|
||||||
|
|
||||||
def test_attribute_mixing_dict_directed(self): |
|
||||||
d = nx.attribute_mixing_dict(self.D, "fish") |
|
||||||
d_result = { |
|
||||||
"one": {"one": 1, "red": 1}, |
|
||||||
"two": {"two": 1, "blue": 1}, |
|
||||||
"red": {}, |
|
||||||
"blue": {}, |
|
||||||
} |
|
||||||
assert d == d_result |
|
||||||
|
|
||||||
def test_attribute_mixing_dict_multigraph(self): |
|
||||||
d = nx.attribute_mixing_dict(self.M, "fish") |
|
||||||
d_result = {"one": {"one": 4}, "two": {"two": 2}} |
|
||||||
assert d == d_result |
|
||||||
|
|
||||||
|
|
||||||
class TestAttributeMixingMatrix(BaseTestAttributeMixing): |
|
||||||
def test_attribute_mixing_matrix_undirected(self): |
|
||||||
mapping = {"one": 0, "two": 1, "red": 2, "blue": 3} |
|
||||||
a_result = np.array([[2, 0, 1, 0], [0, 2, 0, 1], [1, 0, 0, 0], [0, 1, 0, 0]]) |
|
||||||
a = nx.attribute_mixing_matrix( |
|
||||||
self.G, "fish", mapping=mapping, normalized=False |
|
||||||
) |
|
||||||
npt.assert_equal(a, a_result) |
|
||||||
a = nx.attribute_mixing_matrix(self.G, "fish", mapping=mapping) |
|
||||||
npt.assert_equal(a, a_result / float(a_result.sum())) |
|
||||||
|
|
||||||
def test_attribute_mixing_matrix_directed(self): |
|
||||||
mapping = {"one": 0, "two": 1, "red": 2, "blue": 3} |
|
||||||
a_result = np.array([[1, 0, 1, 0], [0, 1, 0, 1], [0, 0, 0, 0], [0, 0, 0, 0]]) |
|
||||||
a = nx.attribute_mixing_matrix( |
|
||||||
self.D, "fish", mapping=mapping, normalized=False |
|
||||||
) |
|
||||||
npt.assert_equal(a, a_result) |
|
||||||
a = nx.attribute_mixing_matrix(self.D, "fish", mapping=mapping) |
|
||||||
npt.assert_equal(a, a_result / float(a_result.sum())) |
|
||||||
|
|
||||||
def test_attribute_mixing_matrix_multigraph(self): |
|
||||||
mapping = {"one": 0, "two": 1, "red": 2, "blue": 3} |
|
||||||
a_result = np.array([[4, 0, 0, 0], [0, 2, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]) |
|
||||||
a = nx.attribute_mixing_matrix( |
|
||||||
self.M, "fish", mapping=mapping, normalized=False |
|
||||||
) |
|
||||||
npt.assert_equal(a, a_result) |
|
||||||
a = nx.attribute_mixing_matrix(self.M, "fish", mapping=mapping) |
|
||||||
npt.assert_equal(a, a_result / float(a_result.sum())) |
|
@ -1,76 +0,0 @@ |
|||||||
import networkx as nx |
|
||||||
from networkx.testing import almost_equal |
|
||||||
|
|
||||||
|
|
||||||
class TestAverageNeighbor: |
|
||||||
def test_degree_p4(self): |
|
||||||
G = nx.path_graph(4) |
|
||||||
answer = {0: 2, 1: 1.5, 2: 1.5, 3: 2} |
|
||||||
nd = nx.average_neighbor_degree(G) |
|
||||||
assert nd == answer |
|
||||||
|
|
||||||
D = G.to_directed() |
|
||||||
nd = nx.average_neighbor_degree(D) |
|
||||||
assert nd == answer |
|
||||||
|
|
||||||
D = G.to_directed() |
|
||||||
nd = nx.average_neighbor_degree(D) |
|
||||||
assert nd == answer |
|
||||||
|
|
||||||
D = G.to_directed() |
|
||||||
nd = nx.average_neighbor_degree(D, source="in", target="in") |
|
||||||
assert nd == answer |
|
||||||
|
|
||||||
def test_degree_p4_weighted(self): |
|
||||||
G = nx.path_graph(4) |
|
||||||
G[1][2]["weight"] = 4 |
|
||||||
answer = {0: 2, 1: 1.8, 2: 1.8, 3: 2} |
|
||||||
nd = nx.average_neighbor_degree(G, weight="weight") |
|
||||||
assert nd == answer |
|
||||||
|
|
||||||
D = G.to_directed() |
|
||||||
nd = nx.average_neighbor_degree(D, weight="weight") |
|
||||||
assert nd == answer |
|
||||||
|
|
||||||
D = G.to_directed() |
|
||||||
nd = nx.average_neighbor_degree(D, weight="weight") |
|
||||||
assert nd == answer |
|
||||||
nd = nx.average_neighbor_degree(D, source="out", target="out", weight="weight") |
|
||||||
assert nd == answer |
|
||||||
|
|
||||||
D = G.to_directed() |
|
||||||
nd = nx.average_neighbor_degree(D, source="in", target="in", weight="weight") |
|
||||||
assert nd == answer |
|
||||||
|
|
||||||
def test_degree_k4(self): |
|
||||||
G = nx.complete_graph(4) |
|
||||||
answer = {0: 3, 1: 3, 2: 3, 3: 3} |
|
||||||
nd = nx.average_neighbor_degree(G) |
|
||||||
assert nd == answer |
|
||||||
|
|
||||||
D = G.to_directed() |
|
||||||
nd = nx.average_neighbor_degree(D) |
|
||||||
assert nd == answer |
|
||||||
|
|
||||||
D = G.to_directed() |
|
||||||
nd = nx.average_neighbor_degree(D) |
|
||||||
assert nd == answer |
|
||||||
|
|
||||||
D = G.to_directed() |
|
||||||
nd = nx.average_neighbor_degree(D, source="in", target="in") |
|
||||||
assert nd == answer |
|
||||||
|
|
||||||
def test_degree_k4_nodes(self): |
|
||||||
G = nx.complete_graph(4) |
|
||||||
answer = {1: 3.0, 2: 3.0} |
|
||||||
nd = nx.average_neighbor_degree(G, nodes=[1, 2]) |
|
||||||
assert nd == answer |
|
||||||
|
|
||||||
def test_degree_barrat(self): |
|
||||||
G = nx.star_graph(5) |
|
||||||
G.add_edges_from([(5, 6), (5, 7), (5, 8), (5, 9)]) |
|
||||||
G[0][5]["weight"] = 5 |
|
||||||
nd = nx.average_neighbor_degree(G)[5] |
|
||||||
assert nd == 1.8 |
|
||||||
nd = nx.average_neighbor_degree(G, weight="weight")[5] |
|
||||||
assert almost_equal(nd, 3.222222, places=5) |
|
@ -1,86 +0,0 @@ |
|||||||
import networkx as nx |
|
||||||
from .base_test import BaseTestAttributeMixing, BaseTestDegreeMixing |
|
||||||
|
|
||||||
|
|
||||||
class TestAttributeMixingXY(BaseTestAttributeMixing): |
|
||||||
def test_node_attribute_xy_undirected(self): |
|
||||||
attrxy = sorted(nx.node_attribute_xy(self.G, "fish")) |
|
||||||
attrxy_result = sorted( |
|
||||||
[ |
|
||||||
("one", "one"), |
|
||||||
("one", "one"), |
|
||||||
("two", "two"), |
|
||||||
("two", "two"), |
|
||||||
("one", "red"), |
|
||||||
("red", "one"), |
|
||||||
("blue", "two"), |
|
||||||
("two", "blue"), |
|
||||||
] |
|
||||||
) |
|
||||||
assert attrxy == attrxy_result |
|
||||||
|
|
||||||
def test_node_attribute_xy_undirected_nodes(self): |
|
||||||
attrxy = sorted(nx.node_attribute_xy(self.G, "fish", nodes=["one", "yellow"])) |
|
||||||
attrxy_result = sorted([]) |
|
||||||
assert attrxy == attrxy_result |
|
||||||
|
|
||||||
def test_node_attribute_xy_directed(self): |
|
||||||
attrxy = sorted(nx.node_attribute_xy(self.D, "fish")) |
|
||||||
attrxy_result = sorted( |
|
||||||
[("one", "one"), ("two", "two"), ("one", "red"), ("two", "blue")] |
|
||||||
) |
|
||||||
assert attrxy == attrxy_result |
|
||||||
|
|
||||||
def test_node_attribute_xy_multigraph(self): |
|
||||||
attrxy = sorted(nx.node_attribute_xy(self.M, "fish")) |
|
||||||
attrxy_result = [ |
|
||||||
("one", "one"), |
|
||||||
("one", "one"), |
|
||||||
("one", "one"), |
|
||||||
("one", "one"), |
|
||||||
("two", "two"), |
|
||||||
("two", "two"), |
|
||||||
] |
|
||||||
assert attrxy == attrxy_result |
|
||||||
|
|
||||||
def test_node_attribute_xy_selfloop(self): |
|
||||||
attrxy = sorted(nx.node_attribute_xy(self.S, "fish")) |
|
||||||
attrxy_result = [("one", "one"), ("two", "two")] |
|
||||||
assert attrxy == attrxy_result |
|
||||||
|
|
||||||
|
|
||||||
class TestDegreeMixingXY(BaseTestDegreeMixing): |
|
||||||
def test_node_degree_xy_undirected(self): |
|
||||||
xy = sorted(nx.node_degree_xy(self.P4)) |
|
||||||
xy_result = sorted([(1, 2), (2, 1), (2, 2), (2, 2), (1, 2), (2, 1)]) |
|
||||||
assert xy == xy_result |
|
||||||
|
|
||||||
def test_node_degree_xy_undirected_nodes(self): |
|
||||||
xy = sorted(nx.node_degree_xy(self.P4, nodes=[0, 1, -1])) |
|
||||||
xy_result = sorted([(1, 2), (2, 1)]) |
|
||||||
assert xy == xy_result |
|
||||||
|
|
||||||
def test_node_degree_xy_directed(self): |
|
||||||
xy = sorted(nx.node_degree_xy(self.D)) |
|
||||||
xy_result = sorted([(2, 1), (2, 3), (1, 3), (1, 3)]) |
|
||||||
assert xy == xy_result |
|
||||||
|
|
||||||
def test_node_degree_xy_multigraph(self): |
|
||||||
xy = sorted(nx.node_degree_xy(self.M)) |
|
||||||
xy_result = sorted( |
|
||||||
[(2, 3), (2, 3), (3, 2), (3, 2), (2, 3), (3, 2), (1, 2), (2, 1)] |
|
||||||
) |
|
||||||
assert xy == xy_result |
|
||||||
|
|
||||||
def test_node_degree_xy_selfloop(self): |
|
||||||
xy = sorted(nx.node_degree_xy(self.S)) |
|
||||||
xy_result = sorted([(2, 2), (2, 2)]) |
|
||||||
assert xy == xy_result |
|
||||||
|
|
||||||
def test_node_degree_xy_weighted(self): |
|
||||||
G = nx.Graph() |
|
||||||
G.add_edge(1, 2, weight=7) |
|
||||||
G.add_edge(2, 3, weight=10) |
|
||||||
xy = sorted(nx.node_degree_xy(G, weight="weight")) |
|
||||||
xy_result = sorted([(7, 17), (17, 10), (17, 7), (10, 17)]) |
|
||||||
assert xy == xy_result |
|
@ -1,168 +0,0 @@ |
|||||||
""" |
|
||||||
Algorithms for asteroidal triples and asteroidal numbers in graphs. |
|
||||||
|
|
||||||
An asteroidal triple in a graph G is a set of three non-adjacent vertices |
|
||||||
u, v and w such that there exist a path between any two of them that avoids |
|
||||||
closed neighborhood of the third. More formally, v_j, v_k belongs to the same |
|
||||||
connected component of G - N[v_i], where N[v_i] denotes the closed neighborhood |
|
||||||
of v_i. A graph which does not contain any asteroidal triples is called |
|
||||||
an AT-free graph. The class of AT-free graphs is a graph class for which |
|
||||||
many NP-complete problems are solvable in polynomial time. Amongst them, |
|
||||||
independent set and coloring. |
|
||||||
""" |
|
||||||
import networkx as nx |
|
||||||
from networkx.utils import not_implemented_for |
|
||||||
|
|
||||||
__all__ = ["is_at_free", "find_asteroidal_triple"] |
|
||||||
|
|
||||||
|
|
||||||
@not_implemented_for("directed") |
|
||||||
@not_implemented_for("multigraph") |
|
||||||
def find_asteroidal_triple(G): |
|
||||||
r"""Find an asteroidal triple in the given graph. |
|
||||||
|
|
||||||
An asteroidal triple is a triple of non-adjacent vertices such that |
|
||||||
there exists a path between any two of them which avoids the closed |
|
||||||
neighborhood of the third. It checks all independent triples of vertices |
|
||||||
and whether they are an asteroidal triple or not. This is done with the |
|
||||||
help of a data structure called a component structure. |
|
||||||
A component structure encodes information about which vertices belongs to |
|
||||||
the same connected component when the closed neighborhood of a given vertex |
|
||||||
is removed from the graph. The algorithm used to check is the trivial |
|
||||||
one, outlined in [1]_, which has a runtime of |
|
||||||
:math:`O(|V||\overline{E} + |V||E|)`, where the second term is the |
|
||||||
creation of the component structure. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX Graph |
|
||||||
The graph to check whether is AT-free or not |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
list or None |
|
||||||
An asteroidal triple is returned as a list of nodes. If no asteroidal |
|
||||||
triple exists, i.e. the graph is AT-free, then None is returned. |
|
||||||
The returned value depends on the certificate parameter. The default |
|
||||||
option is a bool which is True if the graph is AT-free, i.e. the |
|
||||||
given graph contains no asteroidal triples, and False otherwise, i.e. |
|
||||||
if the graph contains at least one asteroidal triple. |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
The component structure and the algorithm is described in [1]_. The current |
|
||||||
implementation implements the trivial algorithm for simple graphs. |
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] Ekkehard Köhler, |
|
||||||
"Recognizing Graphs without asteroidal triples", |
|
||||||
Journal of Discrete Algorithms 2, pages 439-452, 2004. |
|
||||||
https://www.sciencedirect.com/science/article/pii/S157086670400019X |
|
||||||
""" |
|
||||||
V = set(G.nodes) |
|
||||||
|
|
||||||
if len(V) < 6: |
|
||||||
# An asteroidal triple cannot exist in a graph with 5 or less vertices. |
|
||||||
return None |
|
||||||
|
|
||||||
component_structure = create_component_structure(G) |
|
||||||
E_complement = set(nx.complement(G).edges) |
|
||||||
|
|
||||||
for e in E_complement: |
|
||||||
u = e[0] |
|
||||||
v = e[1] |
|
||||||
u_neighborhood = set(G[u]).union([u]) |
|
||||||
v_neighborhood = set(G[v]).union([v]) |
|
||||||
union_of_neighborhoods = u_neighborhood.union(v_neighborhood) |
|
||||||
for w in V - union_of_neighborhoods: |
|
||||||
"""Check for each pair of vertices whether they belong to the |
|
||||||
same connected component when the closed neighborhood of the |
|
||||||
third is removed.""" |
|
||||||
if ( |
|
||||||
component_structure[u][v] == component_structure[u][w] |
|
||||||
and component_structure[v][u] == component_structure[v][w] |
|
||||||
and component_structure[w][u] == component_structure[w][v] |
|
||||||
): |
|
||||||
return [u, v, w] |
|
||||||
|
|
||||||
return None |
|
||||||
|
|
||||||
|
|
||||||
@not_implemented_for("directed") |
|
||||||
@not_implemented_for("multigraph") |
|
||||||
def is_at_free(G): |
|
||||||
"""Check if a graph is AT-free. |
|
||||||
|
|
||||||
The method uses the `find_asteroidal_triple` method to recognize |
|
||||||
an AT-free graph. If no asteroidal triple is found the graph is |
|
||||||
AT-free and True is returned. If at least one asteroidal triple is |
|
||||||
found the graph is not AT-free and False is returned. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX Graph |
|
||||||
The graph to check whether is AT-free or not. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
bool |
|
||||||
True if G is AT-free and False otherwise. |
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
>>> G = nx.Graph([(0, 1), (0, 2), (1, 2), (1, 3), (1, 4), (4, 5)]) |
|
||||||
>>> nx.is_at_free(G) |
|
||||||
True |
|
||||||
|
|
||||||
>>> G = nx.cycle_graph(6) |
|
||||||
>>> nx.is_at_free(G) |
|
||||||
False |
|
||||||
""" |
|
||||||
return find_asteroidal_triple(G) is None |
|
||||||
|
|
||||||
|
|
||||||
@not_implemented_for("directed") |
|
||||||
@not_implemented_for("multigraph") |
|
||||||
def create_component_structure(G): |
|
||||||
r"""Create component structure for G. |
|
||||||
|
|
||||||
A *component structure* is an `nxn` array, denoted `c`, where `n` is |
|
||||||
the number of vertices, where each row and column corresponds to a vertex. |
|
||||||
|
|
||||||
.. math:: |
|
||||||
c_{uv} = \begin{cases} 0, if v \in N[u] \\ |
|
||||||
k, if v \in component k of G \setminus N[u] \end{cases} |
|
||||||
|
|
||||||
Where `k` is an arbitrary label for each component. The structure is used |
|
||||||
to simplify the detection of asteroidal triples. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX Graph |
|
||||||
Undirected, simple graph. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
component_structure : dictionary |
|
||||||
A dictionary of dictionaries, keyed by pairs of vertices. |
|
||||||
|
|
||||||
""" |
|
||||||
V = set(G.nodes) |
|
||||||
component_structure = {} |
|
||||||
for v in V: |
|
||||||
label = 0 |
|
||||||
closed_neighborhood = set(G[v]).union({v}) |
|
||||||
row_dict = {} |
|
||||||
for u in closed_neighborhood: |
|
||||||
row_dict[u] = 0 |
|
||||||
|
|
||||||
G_reduced = G.subgraph(set(G.nodes) - closed_neighborhood) |
|
||||||
for cc in nx.connected_components(G_reduced): |
|
||||||
label += 1 |
|
||||||
for u in cc: |
|
||||||
row_dict[u] = label |
|
||||||
|
|
||||||
component_structure[v] = row_dict |
|
||||||
|
|
||||||
return component_structure |
|
@ -1,86 +0,0 @@ |
|||||||
r""" This module provides functions and operations for bipartite |
|
||||||
graphs. Bipartite graphs `B = (U, V, E)` have two node sets `U,V` and edges in |
|
||||||
`E` that only connect nodes from opposite sets. It is common in the literature |
|
||||||
to use an spatial analogy referring to the two node sets as top and bottom nodes. |
|
||||||
|
|
||||||
The bipartite algorithms are not imported into the networkx namespace |
|
||||||
at the top level so the easiest way to use them is with: |
|
||||||
|
|
||||||
>>> from networkx.algorithms import bipartite |
|
||||||
|
|
||||||
NetworkX does not have a custom bipartite graph class but the Graph() |
|
||||||
or DiGraph() classes can be used to represent bipartite graphs. However, |
|
||||||
you have to keep track of which set each node belongs to, and make |
|
||||||
sure that there is no edge between nodes of the same set. The convention used |
|
||||||
in NetworkX is to use a node attribute named `bipartite` with values 0 or 1 to |
|
||||||
identify the sets each node belongs to. This convention is not enforced in |
|
||||||
the source code of bipartite functions, it's only a recommendation. |
|
||||||
|
|
||||||
For example: |
|
||||||
|
|
||||||
>>> B = nx.Graph() |
|
||||||
>>> # Add nodes with the node attribute "bipartite" |
|
||||||
>>> B.add_nodes_from([1, 2, 3, 4], bipartite=0) |
|
||||||
>>> B.add_nodes_from(["a", "b", "c"], bipartite=1) |
|
||||||
>>> # Add edges only between nodes of opposite node sets |
|
||||||
>>> B.add_edges_from([(1, "a"), (1, "b"), (2, "b"), (2, "c"), (3, "c"), (4, "a")]) |
|
||||||
|
|
||||||
Many algorithms of the bipartite module of NetworkX require, as an argument, a |
|
||||||
container with all the nodes that belong to one set, in addition to the bipartite |
|
||||||
graph `B`. The functions in the bipartite package do not check that the node set |
|
||||||
is actually correct nor that the input graph is actually bipartite. |
|
||||||
If `B` is connected, you can find the two node sets using a two-coloring |
|
||||||
algorithm: |
|
||||||
|
|
||||||
>>> nx.is_connected(B) |
|
||||||
True |
|
||||||
>>> bottom_nodes, top_nodes = bipartite.sets(B) |
|
||||||
|
|
||||||
However, if the input graph is not connected, there are more than one possible |
|
||||||
colorations. This is the reason why we require the user to pass a container |
|
||||||
with all nodes of one bipartite node set as an argument to most bipartite |
|
||||||
functions. In the face of ambiguity, we refuse the temptation to guess and |
|
||||||
raise an :exc:`AmbiguousSolution <networkx.AmbiguousSolution>` |
|
||||||
Exception if the input graph for |
|
||||||
:func:`bipartite.sets <networkx.algorithms.bipartite.basic.sets>` |
|
||||||
is disconnected. |
|
||||||
|
|
||||||
Using the `bipartite` node attribute, you can easily get the two node sets: |
|
||||||
|
|
||||||
>>> top_nodes = {n for n, d in B.nodes(data=True) if d["bipartite"] == 0} |
|
||||||
>>> bottom_nodes = set(B) - top_nodes |
|
||||||
|
|
||||||
So you can easily use the bipartite algorithms that require, as an argument, a |
|
||||||
container with all nodes that belong to one node set: |
|
||||||
|
|
||||||
>>> print(round(bipartite.density(B, bottom_nodes), 2)) |
|
||||||
0.5 |
|
||||||
>>> G = bipartite.projected_graph(B, top_nodes) |
|
||||||
|
|
||||||
All bipartite graph generators in NetworkX build bipartite graphs with the |
|
||||||
`bipartite` node attribute. Thus, you can use the same approach: |
|
||||||
|
|
||||||
>>> RB = bipartite.random_graph(5, 7, 0.2) |
|
||||||
>>> RB_top = {n for n, d in RB.nodes(data=True) if d["bipartite"] == 0} |
|
||||||
>>> RB_bottom = set(RB) - RB_top |
|
||||||
>>> list(RB_top) |
|
||||||
[0, 1, 2, 3, 4] |
|
||||||
>>> list(RB_bottom) |
|
||||||
[5, 6, 7, 8, 9, 10, 11] |
|
||||||
|
|
||||||
For other bipartite graph generators see |
|
||||||
:mod:`Generators <networkx.algorithms.bipartite.generators>`. |
|
||||||
|
|
||||||
""" |
|
||||||
|
|
||||||
from networkx.algorithms.bipartite.basic import * |
|
||||||
from networkx.algorithms.bipartite.centrality import * |
|
||||||
from networkx.algorithms.bipartite.cluster import * |
|
||||||
from networkx.algorithms.bipartite.covering import * |
|
||||||
from networkx.algorithms.bipartite.edgelist import * |
|
||||||
from networkx.algorithms.bipartite.matching import * |
|
||||||
from networkx.algorithms.bipartite.matrix import * |
|
||||||
from networkx.algorithms.bipartite.projection import * |
|
||||||
from networkx.algorithms.bipartite.redundancy import * |
|
||||||
from networkx.algorithms.bipartite.spectral import * |
|
||||||
from networkx.algorithms.bipartite.generators import * |
|
@ -1,303 +0,0 @@ |
|||||||
""" |
|
||||||
========================== |
|
||||||
Bipartite Graph Algorithms |
|
||||||
========================== |
|
||||||
""" |
|
||||||
import networkx as nx |
|
||||||
from networkx.algorithms.components import connected_components |
|
||||||
|
|
||||||
__all__ = [ |
|
||||||
"is_bipartite", |
|
||||||
"is_bipartite_node_set", |
|
||||||
"color", |
|
||||||
"sets", |
|
||||||
"density", |
|
||||||
"degrees", |
|
||||||
] |
|
||||||
|
|
||||||
|
|
||||||
def color(G): |
|
||||||
"""Returns a two-coloring of the graph. |
|
||||||
|
|
||||||
Raises an exception if the graph is not bipartite. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
color : dictionary |
|
||||||
A dictionary keyed by node with a 1 or 0 as data for each node color. |
|
||||||
|
|
||||||
Raises |
|
||||||
------ |
|
||||||
NetworkXError |
|
||||||
If the graph is not two-colorable. |
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
>>> from networkx.algorithms import bipartite |
|
||||||
>>> G = nx.path_graph(4) |
|
||||||
>>> c = bipartite.color(G) |
|
||||||
>>> print(c) |
|
||||||
{0: 1, 1: 0, 2: 1, 3: 0} |
|
||||||
|
|
||||||
You can use this to set a node attribute indicating the biparite set: |
|
||||||
|
|
||||||
>>> nx.set_node_attributes(G, c, "bipartite") |
|
||||||
>>> print(G.nodes[0]["bipartite"]) |
|
||||||
1 |
|
||||||
>>> print(G.nodes[1]["bipartite"]) |
|
||||||
0 |
|
||||||
""" |
|
||||||
if G.is_directed(): |
|
||||||
import itertools |
|
||||||
|
|
||||||
def neighbors(v): |
|
||||||
return itertools.chain.from_iterable([G.predecessors(v), G.successors(v)]) |
|
||||||
|
|
||||||
else: |
|
||||||
neighbors = G.neighbors |
|
||||||
|
|
||||||
color = {} |
|
||||||
for n in G: # handle disconnected graphs |
|
||||||
if n in color or len(G[n]) == 0: # skip isolates |
|
||||||
continue |
|
||||||
queue = [n] |
|
||||||
color[n] = 1 # nodes seen with color (1 or 0) |
|
||||||
while queue: |
|
||||||
v = queue.pop() |
|
||||||
c = 1 - color[v] # opposite color of node v |
|
||||||
for w in neighbors(v): |
|
||||||
if w in color: |
|
||||||
if color[w] == color[v]: |
|
||||||
raise nx.NetworkXError("Graph is not bipartite.") |
|
||||||
else: |
|
||||||
color[w] = c |
|
||||||
queue.append(w) |
|
||||||
# color isolates with 0 |
|
||||||
color.update(dict.fromkeys(nx.isolates(G), 0)) |
|
||||||
return color |
|
||||||
|
|
||||||
|
|
||||||
def is_bipartite(G): |
|
||||||
""" Returns True if graph G is bipartite, False if not. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
>>> from networkx.algorithms import bipartite |
|
||||||
>>> G = nx.path_graph(4) |
|
||||||
>>> print(bipartite.is_bipartite(G)) |
|
||||||
True |
|
||||||
|
|
||||||
See Also |
|
||||||
-------- |
|
||||||
color, is_bipartite_node_set |
|
||||||
""" |
|
||||||
try: |
|
||||||
color(G) |
|
||||||
return True |
|
||||||
except nx.NetworkXError: |
|
||||||
return False |
|
||||||
|
|
||||||
|
|
||||||
def is_bipartite_node_set(G, nodes): |
|
||||||
"""Returns True if nodes and G/nodes are a bipartition of G. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
|
|
||||||
nodes: list or container |
|
||||||
Check if nodes are a one of a bipartite set. |
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
>>> from networkx.algorithms import bipartite |
|
||||||
>>> G = nx.path_graph(4) |
|
||||||
>>> X = set([1, 3]) |
|
||||||
>>> bipartite.is_bipartite_node_set(G, X) |
|
||||||
True |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
For connected graphs the bipartite sets are unique. This function handles |
|
||||||
disconnected graphs. |
|
||||||
""" |
|
||||||
S = set(nodes) |
|
||||||
for CC in (G.subgraph(c).copy() for c in connected_components(G)): |
|
||||||
X, Y = sets(CC) |
|
||||||
if not ( |
|
||||||
(X.issubset(S) and Y.isdisjoint(S)) or (Y.issubset(S) and X.isdisjoint(S)) |
|
||||||
): |
|
||||||
return False |
|
||||||
return True |
|
||||||
|
|
||||||
|
|
||||||
def sets(G, top_nodes=None): |
|
||||||
"""Returns bipartite node sets of graph G. |
|
||||||
|
|
||||||
Raises an exception if the graph is not bipartite or if the input |
|
||||||
graph is disconnected and thus more than one valid solution exists. |
|
||||||
See :mod:`bipartite documentation <networkx.algorithms.bipartite>` |
|
||||||
for further details on how bipartite graphs are handled in NetworkX. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
|
|
||||||
top_nodes : container, optional |
|
||||||
Container with all nodes in one bipartite node set. If not supplied |
|
||||||
it will be computed. But if more than one solution exists an exception |
|
||||||
will be raised. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
X : set |
|
||||||
Nodes from one side of the bipartite graph. |
|
||||||
Y : set |
|
||||||
Nodes from the other side. |
|
||||||
|
|
||||||
Raises |
|
||||||
------ |
|
||||||
AmbiguousSolution |
|
||||||
Raised if the input bipartite graph is disconnected and no container |
|
||||||
with all nodes in one bipartite set is provided. When determining |
|
||||||
the nodes in each bipartite set more than one valid solution is |
|
||||||
possible if the input graph is disconnected. |
|
||||||
NetworkXError |
|
||||||
Raised if the input graph is not bipartite. |
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
>>> from networkx.algorithms import bipartite |
|
||||||
>>> G = nx.path_graph(4) |
|
||||||
>>> X, Y = bipartite.sets(G) |
|
||||||
>>> list(X) |
|
||||||
[0, 2] |
|
||||||
>>> list(Y) |
|
||||||
[1, 3] |
|
||||||
|
|
||||||
See Also |
|
||||||
-------- |
|
||||||
color |
|
||||||
|
|
||||||
""" |
|
||||||
if G.is_directed(): |
|
||||||
is_connected = nx.is_weakly_connected |
|
||||||
else: |
|
||||||
is_connected = nx.is_connected |
|
||||||
if top_nodes is not None: |
|
||||||
X = set(top_nodes) |
|
||||||
Y = set(G) - X |
|
||||||
else: |
|
||||||
if not is_connected(G): |
|
||||||
msg = "Disconnected graph: Ambiguous solution for bipartite sets." |
|
||||||
raise nx.AmbiguousSolution(msg) |
|
||||||
c = color(G) |
|
||||||
X = {n for n, is_top in c.items() if is_top} |
|
||||||
Y = {n for n, is_top in c.items() if not is_top} |
|
||||||
return (X, Y) |
|
||||||
|
|
||||||
|
|
||||||
def density(B, nodes): |
|
||||||
"""Returns density of bipartite graph B. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
|
|
||||||
nodes: list or container |
|
||||||
Nodes in one node set of the bipartite graph. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
d : float |
|
||||||
The bipartite density |
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
>>> from networkx.algorithms import bipartite |
|
||||||
>>> G = nx.complete_bipartite_graph(3, 2) |
|
||||||
>>> X = set([0, 1, 2]) |
|
||||||
>>> bipartite.density(G, X) |
|
||||||
1.0 |
|
||||||
>>> Y = set([3, 4]) |
|
||||||
>>> bipartite.density(G, Y) |
|
||||||
1.0 |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
The container of nodes passed as argument must contain all nodes |
|
||||||
in one of the two bipartite node sets to avoid ambiguity in the |
|
||||||
case of disconnected graphs. |
|
||||||
See :mod:`bipartite documentation <networkx.algorithms.bipartite>` |
|
||||||
for further details on how bipartite graphs are handled in NetworkX. |
|
||||||
|
|
||||||
See Also |
|
||||||
-------- |
|
||||||
color |
|
||||||
""" |
|
||||||
n = len(B) |
|
||||||
m = nx.number_of_edges(B) |
|
||||||
nb = len(nodes) |
|
||||||
nt = n - nb |
|
||||||
if m == 0: # includes cases n==0 and n==1 |
|
||||||
d = 0.0 |
|
||||||
else: |
|
||||||
if B.is_directed(): |
|
||||||
d = m / (2.0 * float(nb * nt)) |
|
||||||
else: |
|
||||||
d = m / float(nb * nt) |
|
||||||
return d |
|
||||||
|
|
||||||
|
|
||||||
def degrees(B, nodes, weight=None): |
|
||||||
"""Returns the degrees of the two node sets in the bipartite graph B. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
|
|
||||||
nodes: list or container |
|
||||||
Nodes in one node set of the bipartite graph. |
|
||||||
|
|
||||||
weight : string or None, optional (default=None) |
|
||||||
The edge attribute that holds the numerical value used as a weight. |
|
||||||
If None, then each edge has weight 1. |
|
||||||
The degree is the sum of the edge weights adjacent to the node. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
(degX,degY) : tuple of dictionaries |
|
||||||
The degrees of the two bipartite sets as dictionaries keyed by node. |
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
>>> from networkx.algorithms import bipartite |
|
||||||
>>> G = nx.complete_bipartite_graph(3, 2) |
|
||||||
>>> Y = set([3, 4]) |
|
||||||
>>> degX, degY = bipartite.degrees(G, Y) |
|
||||||
>>> dict(degX) |
|
||||||
{0: 2, 1: 2, 2: 2} |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
The container of nodes passed as argument must contain all nodes |
|
||||||
in one of the two bipartite node sets to avoid ambiguity in the |
|
||||||
case of disconnected graphs. |
|
||||||
See :mod:`bipartite documentation <networkx.algorithms.bipartite>` |
|
||||||
for further details on how bipartite graphs are handled in NetworkX. |
|
||||||
|
|
||||||
See Also |
|
||||||
-------- |
|
||||||
color, density |
|
||||||
""" |
|
||||||
bottom = set(nodes) |
|
||||||
top = set(B) - bottom |
|
||||||
return (B.degree(top, weight), B.degree(bottom, weight)) |
|
@ -1,268 +0,0 @@ |
|||||||
import networkx as nx |
|
||||||
|
|
||||||
__all__ = ["degree_centrality", "betweenness_centrality", "closeness_centrality"] |
|
||||||
|
|
||||||
|
|
||||||
def degree_centrality(G, nodes): |
|
||||||
r"""Compute the degree centrality for nodes in a bipartite network. |
|
||||||
|
|
||||||
The degree centrality for a node `v` is the fraction of nodes |
|
||||||
connected to it. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : graph |
|
||||||
A bipartite network |
|
||||||
|
|
||||||
nodes : list or container |
|
||||||
Container with all nodes in one bipartite node set. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
centrality : dictionary |
|
||||||
Dictionary keyed by node with bipartite degree centrality as the value. |
|
||||||
|
|
||||||
See Also |
|
||||||
-------- |
|
||||||
betweenness_centrality, |
|
||||||
closeness_centrality, |
|
||||||
sets, |
|
||||||
is_bipartite |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
The nodes input parameter must contain all nodes in one bipartite node set, |
|
||||||
but the dictionary returned contains all nodes from both bipartite node |
|
||||||
sets. See :mod:`bipartite documentation <networkx.algorithms.bipartite>` |
|
||||||
for further details on how bipartite graphs are handled in NetworkX. |
|
||||||
|
|
||||||
For unipartite networks, the degree centrality values are |
|
||||||
normalized by dividing by the maximum possible degree (which is |
|
||||||
`n-1` where `n` is the number of nodes in G). |
|
||||||
|
|
||||||
In the bipartite case, the maximum possible degree of a node in a |
|
||||||
bipartite node set is the number of nodes in the opposite node set |
|
||||||
[1]_. The degree centrality for a node `v` in the bipartite |
|
||||||
sets `U` with `n` nodes and `V` with `m` nodes is |
|
||||||
|
|
||||||
.. math:: |
|
||||||
|
|
||||||
d_{v} = \frac{deg(v)}{m}, \mbox{for} v \in U , |
|
||||||
|
|
||||||
d_{v} = \frac{deg(v)}{n}, \mbox{for} v \in V , |
|
||||||
|
|
||||||
|
|
||||||
where `deg(v)` is the degree of node `v`. |
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] Borgatti, S.P. and Halgin, D. In press. "Analyzing Affiliation |
|
||||||
Networks". In Carrington, P. and Scott, J. (eds) The Sage Handbook |
|
||||||
of Social Network Analysis. Sage Publications. |
|
||||||
http://www.steveborgatti.com/research/publications/bhaffiliations.pdf |
|
||||||
""" |
|
||||||
top = set(nodes) |
|
||||||
bottom = set(G) - top |
|
||||||
s = 1.0 / len(bottom) |
|
||||||
centrality = {n: d * s for n, d in G.degree(top)} |
|
||||||
s = 1.0 / len(top) |
|
||||||
centrality.update({n: d * s for n, d in G.degree(bottom)}) |
|
||||||
return centrality |
|
||||||
|
|
||||||
|
|
||||||
def betweenness_centrality(G, nodes): |
|
||||||
r"""Compute betweenness centrality for nodes in a bipartite network. |
|
||||||
|
|
||||||
Betweenness centrality of a node `v` is the sum of the |
|
||||||
fraction of all-pairs shortest paths that pass through `v`. |
|
||||||
|
|
||||||
Values of betweenness are normalized by the maximum possible |
|
||||||
value which for bipartite graphs is limited by the relative size |
|
||||||
of the two node sets [1]_. |
|
||||||
|
|
||||||
Let `n` be the number of nodes in the node set `U` and |
|
||||||
`m` be the number of nodes in the node set `V`, then |
|
||||||
nodes in `U` are normalized by dividing by |
|
||||||
|
|
||||||
.. math:: |
|
||||||
|
|
||||||
\frac{1}{2} [m^2 (s + 1)^2 + m (s + 1)(2t - s - 1) - t (2s - t + 3)] , |
|
||||||
|
|
||||||
where |
|
||||||
|
|
||||||
.. math:: |
|
||||||
|
|
||||||
s = (n - 1) \div m , t = (n - 1) \mod m , |
|
||||||
|
|
||||||
and nodes in `V` are normalized by dividing by |
|
||||||
|
|
||||||
.. math:: |
|
||||||
|
|
||||||
\frac{1}{2} [n^2 (p + 1)^2 + n (p + 1)(2r - p - 1) - r (2p - r + 3)] , |
|
||||||
|
|
||||||
where, |
|
||||||
|
|
||||||
.. math:: |
|
||||||
|
|
||||||
p = (m - 1) \div n , r = (m - 1) \mod n . |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : graph |
|
||||||
A bipartite graph |
|
||||||
|
|
||||||
nodes : list or container |
|
||||||
Container with all nodes in one bipartite node set. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
betweenness : dictionary |
|
||||||
Dictionary keyed by node with bipartite betweenness centrality |
|
||||||
as the value. |
|
||||||
|
|
||||||
See Also |
|
||||||
-------- |
|
||||||
degree_centrality, |
|
||||||
closeness_centrality, |
|
||||||
sets, |
|
||||||
is_bipartite |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
The nodes input parameter must contain all nodes in one bipartite node set, |
|
||||||
but the dictionary returned contains all nodes from both node sets. |
|
||||||
See :mod:`bipartite documentation <networkx.algorithms.bipartite>` |
|
||||||
for further details on how bipartite graphs are handled in NetworkX. |
|
||||||
|
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] Borgatti, S.P. and Halgin, D. In press. "Analyzing Affiliation |
|
||||||
Networks". In Carrington, P. and Scott, J. (eds) The Sage Handbook |
|
||||||
of Social Network Analysis. Sage Publications. |
|
||||||
http://www.steveborgatti.com/research/publications/bhaffiliations.pdf |
|
||||||
""" |
|
||||||
top = set(nodes) |
|
||||||
bottom = set(G) - top |
|
||||||
n = float(len(top)) |
|
||||||
m = float(len(bottom)) |
|
||||||
s = (n - 1) // m |
|
||||||
t = (n - 1) % m |
|
||||||
bet_max_top = ( |
|
||||||
((m ** 2) * ((s + 1) ** 2)) |
|
||||||
+ (m * (s + 1) * (2 * t - s - 1)) |
|
||||||
- (t * ((2 * s) - t + 3)) |
|
||||||
) / 2.0 |
|
||||||
p = (m - 1) // n |
|
||||||
r = (m - 1) % n |
|
||||||
bet_max_bot = ( |
|
||||||
((n ** 2) * ((p + 1) ** 2)) |
|
||||||
+ (n * (p + 1) * (2 * r - p - 1)) |
|
||||||
- (r * ((2 * p) - r + 3)) |
|
||||||
) / 2.0 |
|
||||||
betweenness = nx.betweenness_centrality(G, normalized=False, weight=None) |
|
||||||
for node in top: |
|
||||||
betweenness[node] /= bet_max_top |
|
||||||
for node in bottom: |
|
||||||
betweenness[node] /= bet_max_bot |
|
||||||
return betweenness |
|
||||||
|
|
||||||
|
|
||||||
def closeness_centrality(G, nodes, normalized=True): |
|
||||||
r"""Compute the closeness centrality for nodes in a bipartite network. |
|
||||||
|
|
||||||
The closeness of a node is the distance to all other nodes in the |
|
||||||
graph or in the case that the graph is not connected to all other nodes |
|
||||||
in the connected component containing that node. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : graph |
|
||||||
A bipartite network |
|
||||||
|
|
||||||
nodes : list or container |
|
||||||
Container with all nodes in one bipartite node set. |
|
||||||
|
|
||||||
normalized : bool, optional |
|
||||||
If True (default) normalize by connected component size. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
closeness : dictionary |
|
||||||
Dictionary keyed by node with bipartite closeness centrality |
|
||||||
as the value. |
|
||||||
|
|
||||||
See Also |
|
||||||
-------- |
|
||||||
betweenness_centrality, |
|
||||||
degree_centrality |
|
||||||
sets, |
|
||||||
is_bipartite |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
The nodes input parameter must contain all nodes in one bipartite node set, |
|
||||||
but the dictionary returned contains all nodes from both node sets. |
|
||||||
See :mod:`bipartite documentation <networkx.algorithms.bipartite>` |
|
||||||
for further details on how bipartite graphs are handled in NetworkX. |
|
||||||
|
|
||||||
|
|
||||||
Closeness centrality is normalized by the minimum distance possible. |
|
||||||
In the bipartite case the minimum distance for a node in one bipartite |
|
||||||
node set is 1 from all nodes in the other node set and 2 from all |
|
||||||
other nodes in its own set [1]_. Thus the closeness centrality |
|
||||||
for node `v` in the two bipartite sets `U` with |
|
||||||
`n` nodes and `V` with `m` nodes is |
|
||||||
|
|
||||||
.. math:: |
|
||||||
|
|
||||||
c_{v} = \frac{m + 2(n - 1)}{d}, \mbox{for} v \in U, |
|
||||||
|
|
||||||
c_{v} = \frac{n + 2(m - 1)}{d}, \mbox{for} v \in V, |
|
||||||
|
|
||||||
where `d` is the sum of the distances from `v` to all |
|
||||||
other nodes. |
|
||||||
|
|
||||||
Higher values of closeness indicate higher centrality. |
|
||||||
|
|
||||||
As in the unipartite case, setting normalized=True causes the |
|
||||||
values to normalized further to n-1 / size(G)-1 where n is the |
|
||||||
number of nodes in the connected part of graph containing the |
|
||||||
node. If the graph is not completely connected, this algorithm |
|
||||||
computes the closeness centrality for each connected part |
|
||||||
separately. |
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] Borgatti, S.P. and Halgin, D. In press. "Analyzing Affiliation |
|
||||||
Networks". In Carrington, P. and Scott, J. (eds) The Sage Handbook |
|
||||||
of Social Network Analysis. Sage Publications. |
|
||||||
http://www.steveborgatti.com/research/publications/bhaffiliations.pdf |
|
||||||
""" |
|
||||||
closeness = {} |
|
||||||
path_length = nx.single_source_shortest_path_length |
|
||||||
top = set(nodes) |
|
||||||
bottom = set(G) - top |
|
||||||
n = float(len(top)) |
|
||||||
m = float(len(bottom)) |
|
||||||
for node in top: |
|
||||||
sp = dict(path_length(G, node)) |
|
||||||
totsp = sum(sp.values()) |
|
||||||
if totsp > 0.0 and len(G) > 1: |
|
||||||
closeness[node] = (m + 2 * (n - 1)) / totsp |
|
||||||
if normalized: |
|
||||||
s = (len(sp) - 1.0) / (len(G) - 1) |
|
||||||
closeness[node] *= s |
|
||||||
else: |
|
||||||
closeness[n] = 0.0 |
|
||||||
for node in bottom: |
|
||||||
sp = dict(path_length(G, node)) |
|
||||||
totsp = sum(sp.values()) |
|
||||||
if totsp > 0.0 and len(G) > 1: |
|
||||||
closeness[node] = (n + 2 * (m - 1)) / totsp |
|
||||||
if normalized: |
|
||||||
s = (len(sp) - 1.0) / (len(G) - 1) |
|
||||||
closeness[node] *= s |
|
||||||
else: |
|
||||||
closeness[n] = 0.0 |
|
||||||
return closeness |
|
@ -1,276 +0,0 @@ |
|||||||
"""Functions for computing clustering of pairs |
|
||||||
|
|
||||||
""" |
|
||||||
|
|
||||||
import itertools |
|
||||||
import networkx as nx |
|
||||||
|
|
||||||
__all__ = [ |
|
||||||
"clustering", |
|
||||||
"average_clustering", |
|
||||||
"latapy_clustering", |
|
||||||
"robins_alexander_clustering", |
|
||||||
] |
|
||||||
|
|
||||||
|
|
||||||
def cc_dot(nu, nv): |
|
||||||
return float(len(nu & nv)) / len(nu | nv) |
|
||||||
|
|
||||||
|
|
||||||
def cc_max(nu, nv): |
|
||||||
return float(len(nu & nv)) / max(len(nu), len(nv)) |
|
||||||
|
|
||||||
|
|
||||||
def cc_min(nu, nv): |
|
||||||
return float(len(nu & nv)) / min(len(nu), len(nv)) |
|
||||||
|
|
||||||
|
|
||||||
modes = {"dot": cc_dot, "min": cc_min, "max": cc_max} |
|
||||||
|
|
||||||
|
|
||||||
def latapy_clustering(G, nodes=None, mode="dot"): |
|
||||||
r"""Compute a bipartite clustering coefficient for nodes. |
|
||||||
|
|
||||||
The bipartie clustering coefficient is a measure of local density |
|
||||||
of connections defined as [1]_: |
|
||||||
|
|
||||||
.. math:: |
|
||||||
|
|
||||||
c_u = \frac{\sum_{v \in N(N(u))} c_{uv} }{|N(N(u))|} |
|
||||||
|
|
||||||
where `N(N(u))` are the second order neighbors of `u` in `G` excluding `u`, |
|
||||||
and `c_{uv}` is the pairwise clustering coefficient between nodes |
|
||||||
`u` and `v`. |
|
||||||
|
|
||||||
The mode selects the function for `c_{uv}` which can be: |
|
||||||
|
|
||||||
`dot`: |
|
||||||
|
|
||||||
.. math:: |
|
||||||
|
|
||||||
c_{uv}=\frac{|N(u)\cap N(v)|}{|N(u) \cup N(v)|} |
|
||||||
|
|
||||||
`min`: |
|
||||||
|
|
||||||
.. math:: |
|
||||||
|
|
||||||
c_{uv}=\frac{|N(u)\cap N(v)|}{min(|N(u)|,|N(v)|)} |
|
||||||
|
|
||||||
`max`: |
|
||||||
|
|
||||||
.. math:: |
|
||||||
|
|
||||||
c_{uv}=\frac{|N(u)\cap N(v)|}{max(|N(u)|,|N(v)|)} |
|
||||||
|
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : graph |
|
||||||
A bipartite graph |
|
||||||
|
|
||||||
nodes : list or iterable (optional) |
|
||||||
Compute bipartite clustering for these nodes. The default |
|
||||||
is all nodes in G. |
|
||||||
|
|
||||||
mode : string |
|
||||||
The pariwise bipartite clustering method to be used in the computation. |
|
||||||
It must be "dot", "max", or "min". |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
clustering : dictionary |
|
||||||
A dictionary keyed by node with the clustering coefficient value. |
|
||||||
|
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
>>> from networkx.algorithms import bipartite |
|
||||||
>>> G = nx.path_graph(4) # path graphs are bipartite |
|
||||||
>>> c = bipartite.clustering(G) |
|
||||||
>>> c[0] |
|
||||||
0.5 |
|
||||||
>>> c = bipartite.clustering(G, mode="min") |
|
||||||
>>> c[0] |
|
||||||
1.0 |
|
||||||
|
|
||||||
See Also |
|
||||||
-------- |
|
||||||
robins_alexander_clustering |
|
||||||
square_clustering |
|
||||||
average_clustering |
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] Latapy, Matthieu, Clémence Magnien, and Nathalie Del Vecchio (2008). |
|
||||||
Basic notions for the analysis of large two-mode networks. |
|
||||||
Social Networks 30(1), 31--48. |
|
||||||
""" |
|
||||||
if not nx.algorithms.bipartite.is_bipartite(G): |
|
||||||
raise nx.NetworkXError("Graph is not bipartite") |
|
||||||
|
|
||||||
try: |
|
||||||
cc_func = modes[mode] |
|
||||||
except KeyError as e: |
|
||||||
raise nx.NetworkXError( |
|
||||||
"Mode for bipartite clustering must be: dot, min or max" |
|
||||||
) from e |
|
||||||
|
|
||||||
if nodes is None: |
|
||||||
nodes = G |
|
||||||
ccs = {} |
|
||||||
for v in nodes: |
|
||||||
cc = 0.0 |
|
||||||
nbrs2 = {u for nbr in G[v] for u in G[nbr]} - {v} |
|
||||||
for u in nbrs2: |
|
||||||
cc += cc_func(set(G[u]), set(G[v])) |
|
||||||
if cc > 0.0: # len(nbrs2)>0 |
|
||||||
cc /= len(nbrs2) |
|
||||||
ccs[v] = cc |
|
||||||
return ccs |
|
||||||
|
|
||||||
|
|
||||||
clustering = latapy_clustering |
|
||||||
|
|
||||||
|
|
||||||
def average_clustering(G, nodes=None, mode="dot"): |
|
||||||
r"""Compute the average bipartite clustering coefficient. |
|
||||||
|
|
||||||
A clustering coefficient for the whole graph is the average, |
|
||||||
|
|
||||||
.. math:: |
|
||||||
|
|
||||||
C = \frac{1}{n}\sum_{v \in G} c_v, |
|
||||||
|
|
||||||
where `n` is the number of nodes in `G`. |
|
||||||
|
|
||||||
Similar measures for the two bipartite sets can be defined [1]_ |
|
||||||
|
|
||||||
.. math:: |
|
||||||
|
|
||||||
C_X = \frac{1}{|X|}\sum_{v \in X} c_v, |
|
||||||
|
|
||||||
where `X` is a bipartite set of `G`. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : graph |
|
||||||
a bipartite graph |
|
||||||
|
|
||||||
nodes : list or iterable, optional |
|
||||||
A container of nodes to use in computing the average. |
|
||||||
The nodes should be either the entire graph (the default) or one of the |
|
||||||
bipartite sets. |
|
||||||
|
|
||||||
mode : string |
|
||||||
The pariwise bipartite clustering method. |
|
||||||
It must be "dot", "max", or "min" |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
clustering : float |
|
||||||
The average bipartite clustering for the given set of nodes or the |
|
||||||
entire graph if no nodes are specified. |
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
>>> from networkx.algorithms import bipartite |
|
||||||
>>> G = nx.star_graph(3) # star graphs are bipartite |
|
||||||
>>> bipartite.average_clustering(G) |
|
||||||
0.75 |
|
||||||
>>> X, Y = bipartite.sets(G) |
|
||||||
>>> bipartite.average_clustering(G, X) |
|
||||||
0.0 |
|
||||||
>>> bipartite.average_clustering(G, Y) |
|
||||||
1.0 |
|
||||||
|
|
||||||
See Also |
|
||||||
-------- |
|
||||||
clustering |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
The container of nodes passed to this function must contain all of the nodes |
|
||||||
in one of the bipartite sets ("top" or "bottom") in order to compute |
|
||||||
the correct average bipartite clustering coefficients. |
|
||||||
See :mod:`bipartite documentation <networkx.algorithms.bipartite>` |
|
||||||
for further details on how bipartite graphs are handled in NetworkX. |
|
||||||
|
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] Latapy, Matthieu, Clémence Magnien, and Nathalie Del Vecchio (2008). |
|
||||||
Basic notions for the analysis of large two-mode networks. |
|
||||||
Social Networks 30(1), 31--48. |
|
||||||
""" |
|
||||||
if nodes is None: |
|
||||||
nodes = G |
|
||||||
ccs = latapy_clustering(G, nodes=nodes, mode=mode) |
|
||||||
return float(sum(ccs[v] for v in nodes)) / len(nodes) |
|
||||||
|
|
||||||
|
|
||||||
def robins_alexander_clustering(G): |
|
||||||
r"""Compute the bipartite clustering of G. |
|
||||||
|
|
||||||
Robins and Alexander [1]_ defined bipartite clustering coefficient as |
|
||||||
four times the number of four cycles `C_4` divided by the number of |
|
||||||
three paths `L_3` in a bipartite graph: |
|
||||||
|
|
||||||
.. math:: |
|
||||||
|
|
||||||
CC_4 = \frac{4 * C_4}{L_3} |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : graph |
|
||||||
a bipartite graph |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
clustering : float |
|
||||||
The Robins and Alexander bipartite clustering for the input graph. |
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
>>> from networkx.algorithms import bipartite |
|
||||||
>>> G = nx.davis_southern_women_graph() |
|
||||||
>>> print(round(bipartite.robins_alexander_clustering(G), 3)) |
|
||||||
0.468 |
|
||||||
|
|
||||||
See Also |
|
||||||
-------- |
|
||||||
latapy_clustering |
|
||||||
square_clustering |
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] Robins, G. and M. Alexander (2004). Small worlds among interlocking |
|
||||||
directors: Network structure and distance in bipartite graphs. |
|
||||||
Computational & Mathematical Organization Theory 10(1), 69–94. |
|
||||||
|
|
||||||
""" |
|
||||||
if G.order() < 4 or G.size() < 3: |
|
||||||
return 0 |
|
||||||
L_3 = _threepaths(G) |
|
||||||
if L_3 == 0: |
|
||||||
return 0 |
|
||||||
C_4 = _four_cycles(G) |
|
||||||
return (4.0 * C_4) / L_3 |
|
||||||
|
|
||||||
|
|
||||||
def _four_cycles(G): |
|
||||||
cycles = 0 |
|
||||||
for v in G: |
|
||||||
for u, w in itertools.combinations(G[v], 2): |
|
||||||
cycles += len((set(G[u]) & set(G[w])) - {v}) |
|
||||||
return cycles / 4 |
|
||||||
|
|
||||||
|
|
||||||
def _threepaths(G): |
|
||||||
paths = 0 |
|
||||||
for v in G: |
|
||||||
for u in G[v]: |
|
||||||
for w in set(G[u]) - {v}: |
|
||||||
paths += len(set(G[w]) - {v, u}) |
|
||||||
# Divide by two because we count each three path twice |
|
||||||
# one for each possible starting point |
|
||||||
return paths / 2 |
|
@ -1,55 +0,0 @@ |
|||||||
""" Functions related to graph covers.""" |
|
||||||
|
|
||||||
from networkx.utils import not_implemented_for |
|
||||||
from networkx.algorithms.bipartite.matching import hopcroft_karp_matching |
|
||||||
from networkx.algorithms.covering import min_edge_cover as _min_edge_cover |
|
||||||
|
|
||||||
__all__ = ["min_edge_cover"] |
|
||||||
|
|
||||||
|
|
||||||
@not_implemented_for("directed") |
|
||||||
@not_implemented_for("multigraph") |
|
||||||
def min_edge_cover(G, matching_algorithm=None): |
|
||||||
"""Returns a set of edges which constitutes |
|
||||||
the minimum edge cover of the graph. |
|
||||||
|
|
||||||
The smallest edge cover can be found in polynomial time by finding |
|
||||||
a maximum matching and extending it greedily so that all nodes |
|
||||||
are covered. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
An undirected bipartite graph. |
|
||||||
|
|
||||||
matching_algorithm : function |
|
||||||
A function that returns a maximum cardinality matching in a |
|
||||||
given bipartite graph. The function must take one input, the |
|
||||||
graph ``G``, and return a dictionary mapping each node to its |
|
||||||
mate. If not specified, |
|
||||||
:func:`~networkx.algorithms.bipartite.matching.hopcroft_karp_matching` |
|
||||||
will be used. Other possibilities include |
|
||||||
:func:`~networkx.algorithms.bipartite.matching.eppstein_matching`, |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
set |
|
||||||
A set of the edges in a minimum edge cover of the graph, given as |
|
||||||
pairs of nodes. It contains both the edges `(u, v)` and `(v, u)` |
|
||||||
for given nodes `u` and `v` among the edges of minimum edge cover. |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
An edge cover of a graph is a set of edges such that every node of |
|
||||||
the graph is incident to at least one edge of the set. |
|
||||||
A minimum edge cover is an edge covering of smallest cardinality. |
|
||||||
|
|
||||||
Due to its implementation, the worst-case running time of this algorithm |
|
||||||
is bounded by the worst-case running time of the function |
|
||||||
``matching_algorithm``. |
|
||||||
""" |
|
||||||
if G.order() == 0: # Special case for the empty graph |
|
||||||
return set() |
|
||||||
if matching_algorithm is None: |
|
||||||
matching_algorithm = hopcroft_karp_matching |
|
||||||
return _min_edge_cover(G, matching_algorithm=matching_algorithm) |
|
@ -1,357 +0,0 @@ |
|||||||
""" |
|
||||||
******************** |
|
||||||
Bipartite Edge Lists |
|
||||||
******************** |
|
||||||
Read and write NetworkX graphs as bipartite edge lists. |
|
||||||
|
|
||||||
Format |
|
||||||
------ |
|
||||||
You can read or write three formats of edge lists with these functions. |
|
||||||
|
|
||||||
Node pairs with no data:: |
|
||||||
|
|
||||||
1 2 |
|
||||||
|
|
||||||
Python dictionary as data:: |
|
||||||
|
|
||||||
1 2 {'weight':7, 'color':'green'} |
|
||||||
|
|
||||||
Arbitrary data:: |
|
||||||
|
|
||||||
1 2 7 green |
|
||||||
|
|
||||||
For each edge (u, v) the node u is assigned to part 0 and the node v to part 1. |
|
||||||
""" |
|
||||||
__all__ = ["generate_edgelist", "write_edgelist", "parse_edgelist", "read_edgelist"] |
|
||||||
|
|
||||||
import networkx as nx |
|
||||||
from networkx.utils import open_file, not_implemented_for |
|
||||||
|
|
||||||
|
|
||||||
@open_file(1, mode="wb") |
|
||||||
def write_edgelist(G, path, comments="#", delimiter=" ", data=True, encoding="utf-8"): |
|
||||||
"""Write a bipartite graph as a list of edges. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : Graph |
|
||||||
A NetworkX bipartite graph |
|
||||||
path : file or string |
|
||||||
File or filename to write. If a file is provided, it must be |
|
||||||
opened in 'wb' mode. Filenames ending in .gz or .bz2 will be compressed. |
|
||||||
comments : string, optional |
|
||||||
The character used to indicate the start of a comment |
|
||||||
delimiter : string, optional |
|
||||||
The string used to separate values. The default is whitespace. |
|
||||||
data : bool or list, optional |
|
||||||
If False write no edge data. |
|
||||||
If True write a string representation of the edge data dictionary.. |
|
||||||
If a list (or other iterable) is provided, write the keys specified |
|
||||||
in the list. |
|
||||||
encoding: string, optional |
|
||||||
Specify which encoding to use when writing file. |
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
>>> G = nx.path_graph(4) |
|
||||||
>>> G.add_nodes_from([0, 2], bipartite=0) |
|
||||||
>>> G.add_nodes_from([1, 3], bipartite=1) |
|
||||||
>>> nx.write_edgelist(G, "test.edgelist") |
|
||||||
>>> fh = open("test.edgelist", "wb") |
|
||||||
>>> nx.write_edgelist(G, fh) |
|
||||||
>>> nx.write_edgelist(G, "test.edgelist.gz") |
|
||||||
>>> nx.write_edgelist(G, "test.edgelist.gz", data=False) |
|
||||||
|
|
||||||
>>> G = nx.Graph() |
|
||||||
>>> G.add_edge(1, 2, weight=7, color="red") |
|
||||||
>>> nx.write_edgelist(G, "test.edgelist", data=False) |
|
||||||
>>> nx.write_edgelist(G, "test.edgelist", data=["color"]) |
|
||||||
>>> nx.write_edgelist(G, "test.edgelist", data=["color", "weight"]) |
|
||||||
|
|
||||||
See Also |
|
||||||
-------- |
|
||||||
write_edgelist() |
|
||||||
generate_edgelist() |
|
||||||
""" |
|
||||||
for line in generate_edgelist(G, delimiter, data): |
|
||||||
line += "\n" |
|
||||||
path.write(line.encode(encoding)) |
|
||||||
|
|
||||||
|
|
||||||
@not_implemented_for("directed") |
|
||||||
def generate_edgelist(G, delimiter=" ", data=True): |
|
||||||
"""Generate a single line of the bipartite graph G in edge list format. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
G : NetworkX graph |
|
||||||
The graph is assumed to have node attribute `part` set to 0,1 representing |
|
||||||
the two graph parts |
|
||||||
|
|
||||||
delimiter : string, optional |
|
||||||
Separator for node labels |
|
||||||
|
|
||||||
data : bool or list of keys |
|
||||||
If False generate no edge data. If True use a dictionary |
|
||||||
representation of edge data. If a list of keys use a list of data |
|
||||||
values corresponding to the keys. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
lines : string |
|
||||||
Lines of data in adjlist format. |
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
>>> from networkx.algorithms import bipartite |
|
||||||
>>> G = nx.path_graph(4) |
|
||||||
>>> G.add_nodes_from([0, 2], bipartite=0) |
|
||||||
>>> G.add_nodes_from([1, 3], bipartite=1) |
|
||||||
>>> G[1][2]["weight"] = 3 |
|
||||||
>>> G[2][3]["capacity"] = 12 |
|
||||||
>>> for line in bipartite.generate_edgelist(G, data=False): |
|
||||||
... print(line) |
|
||||||
0 1 |
|
||||||
2 1 |
|
||||||
2 3 |
|
||||||
|
|
||||||
>>> for line in bipartite.generate_edgelist(G): |
|
||||||
... print(line) |
|
||||||
0 1 {} |
|
||||||
2 1 {'weight': 3} |
|
||||||
2 3 {'capacity': 12} |
|
||||||
|
|
||||||
>>> for line in bipartite.generate_edgelist(G, data=["weight"]): |
|
||||||
... print(line) |
|
||||||
0 1 |
|
||||||
2 1 3 |
|
||||||
2 3 |
|
||||||
""" |
|
||||||
try: |
|
||||||
part0 = [n for n, d in G.nodes.items() if d["bipartite"] == 0] |
|
||||||
except BaseException as e: |
|
||||||
raise AttributeError("Missing node attribute `bipartite`") from e |
|
||||||
if data is True or data is False: |
|
||||||
for n in part0: |
|
||||||
for e in G.edges(n, data=data): |
|
||||||
yield delimiter.join(map(str, e)) |
|
||||||
else: |
|
||||||
for n in part0: |
|
||||||
for u, v, d in G.edges(n, data=True): |
|
||||||
e = [u, v] |
|
||||||
try: |
|
||||||
e.extend(d[k] for k in data) |
|
||||||
except KeyError: |
|
||||||
pass # missing data for this edge, should warn? |
|
||||||
yield delimiter.join(map(str, e)) |
|
||||||
|
|
||||||
|
|
||||||
def parse_edgelist( |
|
||||||
lines, comments="#", delimiter=None, create_using=None, nodetype=None, data=True |
|
||||||
): |
|
||||||
"""Parse lines of an edge list representation of a bipartite graph. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
lines : list or iterator of strings |
|
||||||
Input data in edgelist format |
|
||||||
comments : string, optional |
|
||||||
Marker for comment lines |
|
||||||
delimiter : string, optional |
|
||||||
Separator for node labels |
|
||||||
create_using: NetworkX graph container, optional |
|
||||||
Use given NetworkX graph for holding nodes or edges. |
|
||||||
nodetype : Python type, optional |
|
||||||
Convert nodes to this type. |
|
||||||
data : bool or list of (label,type) tuples |
|
||||||
If False generate no edge data or if True use a dictionary |
|
||||||
representation of edge data or a list tuples specifying dictionary |
|
||||||
key names and types for edge data. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
G: NetworkX Graph |
|
||||||
The bipartite graph corresponding to lines |
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
Edgelist with no data: |
|
||||||
|
|
||||||
>>> from networkx.algorithms import bipartite |
|
||||||
>>> lines = ["1 2", "2 3", "3 4"] |
|
||||||
>>> G = bipartite.parse_edgelist(lines, nodetype=int) |
|
||||||
>>> sorted(G.nodes()) |
|
||||||
[1, 2, 3, 4] |
|
||||||
>>> sorted(G.nodes(data=True)) |
|
||||||
[(1, {'bipartite': 0}), (2, {'bipartite': 0}), (3, {'bipartite': 0}), (4, {'bipartite': 1})] |
|
||||||
>>> sorted(G.edges()) |
|
||||||
[(1, 2), (2, 3), (3, 4)] |
|
||||||
|
|
||||||
Edgelist with data in Python dictionary representation: |
|
||||||
|
|
||||||
>>> lines = ["1 2 {'weight':3}", "2 3 {'weight':27}", "3 4 {'weight':3.0}"] |
|
||||||
>>> G = bipartite.parse_edgelist(lines, nodetype=int) |
|
||||||
>>> sorted(G.nodes()) |
|
||||||
[1, 2, 3, 4] |
|
||||||
>>> sorted(G.edges(data=True)) |
|
||||||
[(1, 2, {'weight': 3}), (2, 3, {'weight': 27}), (3, 4, {'weight': 3.0})] |
|
||||||
|
|
||||||
Edgelist with data in a list: |
|
||||||
|
|
||||||
>>> lines = ["1 2 3", "2 3 27", "3 4 3.0"] |
|
||||||
>>> G = bipartite.parse_edgelist(lines, nodetype=int, data=(("weight", float),)) |
|
||||||
>>> sorted(G.nodes()) |
|
||||||
[1, 2, 3, 4] |
|
||||||
>>> sorted(G.edges(data=True)) |
|
||||||
[(1, 2, {'weight': 3.0}), (2, 3, {'weight': 27.0}), (3, 4, {'weight': 3.0})] |
|
||||||
|
|
||||||
See Also |
|
||||||
-------- |
|
||||||
""" |
|
||||||
from ast import literal_eval |
|
||||||
|
|
||||||
G = nx.empty_graph(0, create_using) |
|
||||||
for line in lines: |
|
||||||
p = line.find(comments) |
|
||||||
if p >= 0: |
|
||||||
line = line[:p] |
|
||||||
if not len(line): |
|
||||||
continue |
|
||||||
# split line, should have 2 or more |
|
||||||
s = line.strip().split(delimiter) |
|
||||||
if len(s) < 2: |
|
||||||
continue |
|
||||||
u = s.pop(0) |
|
||||||
v = s.pop(0) |
|
||||||
d = s |
|
||||||
if nodetype is not None: |
|
||||||
try: |
|
||||||
u = nodetype(u) |
|
||||||
v = nodetype(v) |
|
||||||
except BaseException as e: |
|
||||||
raise TypeError( |
|
||||||
f"Failed to convert nodes {u},{v} " f"to type {nodetype}." |
|
||||||
) from e |
|
||||||
|
|
||||||
if len(d) == 0 or data is False: |
|
||||||
# no data or data type specified |
|
||||||
edgedata = {} |
|
||||||
elif data is True: |
|
||||||
# no edge types specified |
|
||||||
try: # try to evaluate as dictionary |
|
||||||
edgedata = dict(literal_eval(" ".join(d))) |
|
||||||
except BaseException as e: |
|
||||||
raise TypeError( |
|
||||||
f"Failed to convert edge data ({d})" f"to dictionary." |
|
||||||
) from e |
|
||||||
else: |
|
||||||
# convert edge data to dictionary with specified keys and type |
|
||||||
if len(d) != len(data): |
|
||||||
raise IndexError( |
|
||||||
f"Edge data {d} and data_keys {data} are not the same length" |
|
||||||
) |
|
||||||
edgedata = {} |
|
||||||
for (edge_key, edge_type), edge_value in zip(data, d): |
|
||||||
try: |
|
||||||
edge_value = edge_type(edge_value) |
|
||||||
except BaseException as e: |
|
||||||
raise TypeError( |
|
||||||
f"Failed to convert {edge_key} data " |
|
||||||
f"{edge_value} to type {edge_type}." |
|
||||||
) from e |
|
||||||
edgedata.update({edge_key: edge_value}) |
|
||||||
G.add_node(u, bipartite=0) |
|
||||||
G.add_node(v, bipartite=1) |
|
||||||
G.add_edge(u, v, **edgedata) |
|
||||||
return G |
|
||||||
|
|
||||||
|
|
||||||
@open_file(0, mode="rb") |
|
||||||
def read_edgelist( |
|
||||||
path, |
|
||||||
comments="#", |
|
||||||
delimiter=None, |
|
||||||
create_using=None, |
|
||||||
nodetype=None, |
|
||||||
data=True, |
|
||||||
edgetype=None, |
|
||||||
encoding="utf-8", |
|
||||||
): |
|
||||||
"""Read a bipartite graph from a list of edges. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
path : file or string |
|
||||||
File or filename to read. If a file is provided, it must be |
|
||||||
opened in 'rb' mode. |
|
||||||
Filenames ending in .gz or .bz2 will be uncompressed. |
|
||||||
comments : string, optional |
|
||||||
The character used to indicate the start of a comment. |
|
||||||
delimiter : string, optional |
|
||||||
The string used to separate values. The default is whitespace. |
|
||||||
create_using : Graph container, optional, |
|
||||||
Use specified container to build graph. The default is networkx.Graph, |
|
||||||
an undirected graph. |
|
||||||
nodetype : int, float, str, Python type, optional |
|
||||||
Convert node data from strings to specified type |
|
||||||
data : bool or list of (label,type) tuples |
|
||||||
Tuples specifying dictionary key names and types for edge data |
|
||||||
edgetype : int, float, str, Python type, optional OBSOLETE |
|
||||||
Convert edge data from strings to specified type and use as 'weight' |
|
||||||
encoding: string, optional |
|
||||||
Specify which encoding to use when reading file. |
|
||||||
|
|
||||||
Returns |
|
||||||
------- |
|
||||||
G : graph |
|
||||||
A networkx Graph or other type specified with create_using |
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
>>> from networkx.algorithms import bipartite |
|
||||||
>>> G = nx.path_graph(4) |
|
||||||
>>> G.add_nodes_from([0, 2], bipartite=0) |
|
||||||
>>> G.add_nodes_from([1, 3], bipartite=1) |
|
||||||
>>> bipartite.write_edgelist(G, "test.edgelist") |
|
||||||
>>> G = bipartite.read_edgelist("test.edgelist") |
|
||||||
|
|
||||||
>>> fh = open("test.edgelist", "rb") |
|
||||||
>>> G = bipartite.read_edgelist(fh) |
|
||||||
>>> fh.close() |
|
||||||
|
|
||||||
>>> G = bipartite.read_edgelist("test.edgelist", nodetype=int) |
|
||||||
|
|
||||||
Edgelist with data in a list: |
|
||||||
|
|
||||||
>>> textline = "1 2 3" |
|
||||||
>>> fh = open("test.edgelist", "w") |
|
||||||
>>> d = fh.write(textline) |
|
||||||
>>> fh.close() |
|
||||||
>>> G = bipartite.read_edgelist( |
|
||||||
... "test.edgelist", nodetype=int, data=(("weight", float),) |
|
||||||
... ) |
|
||||||
>>> list(G) |
|
||||||
[1, 2] |
|
||||||
>>> list(G.edges(data=True)) |
|
||||||
[(1, 2, {'weight': 3.0})] |
|
||||||
|
|
||||||
See parse_edgelist() for more examples of formatting. |
|
||||||
|
|
||||||
See Also |
|
||||||
-------- |
|
||||||
parse_edgelist |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
Since nodes must be hashable, the function nodetype must return hashable |
|
||||||
types (e.g. int, float, str, frozenset - or tuples of those, etc.) |
|
||||||
""" |
|
||||||
lines = (line.decode(encoding) for line in path) |
|
||||||
return parse_edgelist( |
|
||||||
lines, |
|
||||||
comments=comments, |
|
||||||
delimiter=delimiter, |
|
||||||
create_using=create_using, |
|
||||||
nodetype=nodetype, |
|
||||||
data=data, |
|
||||||
) |
|
@ -1,595 +0,0 @@ |
|||||||
""" |
|
||||||
Generators and functions for bipartite graphs. |
|
||||||
""" |
|
||||||
import math |
|
||||||
import numbers |
|
||||||
from functools import reduce |
|
||||||
import networkx as nx |
|
||||||
from networkx.utils import nodes_or_number, py_random_state |
|
||||||
|
|
||||||
__all__ = [ |
|
||||||
"configuration_model", |
|
||||||
"havel_hakimi_graph", |
|
||||||
"reverse_havel_hakimi_graph", |
|
||||||
"alternating_havel_hakimi_graph", |
|
||||||
"preferential_attachment_graph", |
|
||||||
"random_graph", |
|
||||||
"gnmk_random_graph", |
|
||||||
"complete_bipartite_graph", |
|
||||||
] |
|
||||||
|
|
||||||
|
|
||||||
@nodes_or_number([0, 1]) |
|
||||||
def complete_bipartite_graph(n1, n2, create_using=None): |
|
||||||
"""Returns the complete bipartite graph `K_{n_1,n_2}`. |
|
||||||
|
|
||||||
The graph is composed of two partitions with nodes 0 to (n1 - 1) |
|
||||||
in the first and nodes n1 to (n1 + n2 - 1) in the second. |
|
||||||
Each node in the first is connected to each node in the second. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
n1 : integer |
|
||||||
Number of nodes for node set A. |
|
||||||
n2 : integer |
|
||||||
Number of nodes for node set B. |
|
||||||
create_using : NetworkX graph instance, optional |
|
||||||
Return graph of this type. |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
Node labels are the integers 0 to `n_1 + n_2 - 1`. |
|
||||||
|
|
||||||
The nodes are assigned the attribute 'bipartite' with the value 0 or 1 |
|
||||||
to indicate which bipartite set the node belongs to. |
|
||||||
|
|
||||||
This function is not imported in the main namespace. |
|
||||||
To use it use nx.bipartite.complete_bipartite_graph |
|
||||||
""" |
|
||||||
G = nx.empty_graph(0, create_using) |
|
||||||
if G.is_directed(): |
|
||||||
raise nx.NetworkXError("Directed Graph not supported") |
|
||||||
|
|
||||||
n1, top = n1 |
|
||||||
n2, bottom = n2 |
|
||||||
if isinstance(n2, numbers.Integral): |
|
||||||
bottom = [n1 + i for i in bottom] |
|
||||||
G.add_nodes_from(top, bipartite=0) |
|
||||||
G.add_nodes_from(bottom, bipartite=1) |
|
||||||
G.add_edges_from((u, v) for u in top for v in bottom) |
|
||||||
G.graph["name"] = f"complete_bipartite_graph({n1},{n2})" |
|
||||||
return G |
|
||||||
|
|
||||||
|
|
||||||
@py_random_state(3) |
|
||||||
def configuration_model(aseq, bseq, create_using=None, seed=None): |
|
||||||
"""Returns a random bipartite graph from two given degree sequences. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
aseq : list |
|
||||||
Degree sequence for node set A. |
|
||||||
bseq : list |
|
||||||
Degree sequence for node set B. |
|
||||||
create_using : NetworkX graph instance, optional |
|
||||||
Return graph of this type. |
|
||||||
seed : integer, random_state, or None (default) |
|
||||||
Indicator of random number generation state. |
|
||||||
See :ref:`Randomness<randomness>`. |
|
||||||
|
|
||||||
The graph is composed of two partitions. Set A has nodes 0 to |
|
||||||
(len(aseq) - 1) and set B has nodes len(aseq) to (len(bseq) - 1). |
|
||||||
Nodes from set A are connected to nodes in set B by choosing |
|
||||||
randomly from the possible free stubs, one in A and one in B. |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
The sum of the two sequences must be equal: sum(aseq)=sum(bseq) |
|
||||||
If no graph type is specified use MultiGraph with parallel edges. |
|
||||||
If you want a graph with no parallel edges use create_using=Graph() |
|
||||||
but then the resulting degree sequences might not be exact. |
|
||||||
|
|
||||||
The nodes are assigned the attribute 'bipartite' with the value 0 or 1 |
|
||||||
to indicate which bipartite set the node belongs to. |
|
||||||
|
|
||||||
This function is not imported in the main namespace. |
|
||||||
To use it use nx.bipartite.configuration_model |
|
||||||
""" |
|
||||||
G = nx.empty_graph(0, create_using, default=nx.MultiGraph) |
|
||||||
if G.is_directed(): |
|
||||||
raise nx.NetworkXError("Directed Graph not supported") |
|
||||||
|
|
||||||
# length and sum of each sequence |
|
||||||
lena = len(aseq) |
|
||||||
lenb = len(bseq) |
|
||||||
suma = sum(aseq) |
|
||||||
sumb = sum(bseq) |
|
||||||
|
|
||||||
if not suma == sumb: |
|
||||||
raise nx.NetworkXError( |
|
||||||
f"invalid degree sequences, sum(aseq)!=sum(bseq),{suma},{sumb}" |
|
||||||
) |
|
||||||
|
|
||||||
G = _add_nodes_with_bipartite_label(G, lena, lenb) |
|
||||||
|
|
||||||
if len(aseq) == 0 or max(aseq) == 0: |
|
||||||
return G # done if no edges |
|
||||||
|
|
||||||
# build lists of degree-repeated vertex numbers |
|
||||||
stubs = [] |
|
||||||
stubs.extend([[v] * aseq[v] for v in range(0, lena)]) |
|
||||||
astubs = [] |
|
||||||
astubs = [x for subseq in stubs for x in subseq] |
|
||||||
|
|
||||||
stubs = [] |
|
||||||
stubs.extend([[v] * bseq[v - lena] for v in range(lena, lena + lenb)]) |
|
||||||
bstubs = [] |
|
||||||
bstubs = [x for subseq in stubs for x in subseq] |
|
||||||
|
|
||||||
# shuffle lists |
|
||||||
seed.shuffle(astubs) |
|
||||||
seed.shuffle(bstubs) |
|
||||||
|
|
||||||
G.add_edges_from([[astubs[i], bstubs[i]] for i in range(suma)]) |
|
||||||
|
|
||||||
G.name = "bipartite_configuration_model" |
|
||||||
return G |
|
||||||
|
|
||||||
|
|
||||||
def havel_hakimi_graph(aseq, bseq, create_using=None): |
|
||||||
"""Returns a bipartite graph from two given degree sequences using a |
|
||||||
Havel-Hakimi style construction. |
|
||||||
|
|
||||||
The graph is composed of two partitions. Set A has nodes 0 to |
|
||||||
(len(aseq) - 1) and set B has nodes len(aseq) to (len(bseq) - 1). |
|
||||||
Nodes from the set A are connected to nodes in the set B by |
|
||||||
connecting the highest degree nodes in set A to the highest degree |
|
||||||
nodes in set B until all stubs are connected. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
aseq : list |
|
||||||
Degree sequence for node set A. |
|
||||||
bseq : list |
|
||||||
Degree sequence for node set B. |
|
||||||
create_using : NetworkX graph instance, optional |
|
||||||
Return graph of this type. |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
The sum of the two sequences must be equal: sum(aseq)=sum(bseq) |
|
||||||
If no graph type is specified use MultiGraph with parallel edges. |
|
||||||
If you want a graph with no parallel edges use create_using=Graph() |
|
||||||
but then the resulting degree sequences might not be exact. |
|
||||||
|
|
||||||
The nodes are assigned the attribute 'bipartite' with the value 0 or 1 |
|
||||||
to indicate which bipartite set the node belongs to. |
|
||||||
|
|
||||||
This function is not imported in the main namespace. |
|
||||||
To use it use nx.bipartite.havel_hakimi_graph |
|
||||||
""" |
|
||||||
G = nx.empty_graph(0, create_using, default=nx.MultiGraph) |
|
||||||
if G.is_directed(): |
|
||||||
raise nx.NetworkXError("Directed Graph not supported") |
|
||||||
|
|
||||||
# length of the each sequence |
|
||||||
naseq = len(aseq) |
|
||||||
nbseq = len(bseq) |
|
||||||
|
|
||||||
suma = sum(aseq) |
|
||||||
sumb = sum(bseq) |
|
||||||
|
|
||||||
if not suma == sumb: |
|
||||||
raise nx.NetworkXError( |
|
||||||
f"invalid degree sequences, sum(aseq)!=sum(bseq),{suma},{sumb}" |
|
||||||
) |
|
||||||
|
|
||||||
G = _add_nodes_with_bipartite_label(G, naseq, nbseq) |
|
||||||
|
|
||||||
if len(aseq) == 0 or max(aseq) == 0: |
|
||||||
return G # done if no edges |
|
||||||
|
|
||||||
# build list of degree-repeated vertex numbers |
|
||||||
astubs = [[aseq[v], v] for v in range(0, naseq)] |
|
||||||
bstubs = [[bseq[v - naseq], v] for v in range(naseq, naseq + nbseq)] |
|
||||||
astubs.sort() |
|
||||||
while astubs: |
|
||||||
(degree, u) = astubs.pop() # take of largest degree node in the a set |
|
||||||
if degree == 0: |
|
||||||
break # done, all are zero |
|
||||||
# connect the source to largest degree nodes in the b set |
|
||||||
bstubs.sort() |
|
||||||
for target in bstubs[-degree:]: |
|
||||||
v = target[1] |
|
||||||
G.add_edge(u, v) |
|
||||||
target[0] -= 1 # note this updates bstubs too. |
|
||||||
if target[0] == 0: |
|
||||||
bstubs.remove(target) |
|
||||||
|
|
||||||
G.name = "bipartite_havel_hakimi_graph" |
|
||||||
return G |
|
||||||
|
|
||||||
|
|
||||||
def reverse_havel_hakimi_graph(aseq, bseq, create_using=None): |
|
||||||
"""Returns a bipartite graph from two given degree sequences using a |
|
||||||
Havel-Hakimi style construction. |
|
||||||
|
|
||||||
The graph is composed of two partitions. Set A has nodes 0 to |
|
||||||
(len(aseq) - 1) and set B has nodes len(aseq) to (len(bseq) - 1). |
|
||||||
Nodes from set A are connected to nodes in the set B by connecting |
|
||||||
the highest degree nodes in set A to the lowest degree nodes in |
|
||||||
set B until all stubs are connected. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
aseq : list |
|
||||||
Degree sequence for node set A. |
|
||||||
bseq : list |
|
||||||
Degree sequence for node set B. |
|
||||||
create_using : NetworkX graph instance, optional |
|
||||||
Return graph of this type. |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
The sum of the two sequences must be equal: sum(aseq)=sum(bseq) |
|
||||||
If no graph type is specified use MultiGraph with parallel edges. |
|
||||||
If you want a graph with no parallel edges use create_using=Graph() |
|
||||||
but then the resulting degree sequences might not be exact. |
|
||||||
|
|
||||||
The nodes are assigned the attribute 'bipartite' with the value 0 or 1 |
|
||||||
to indicate which bipartite set the node belongs to. |
|
||||||
|
|
||||||
This function is not imported in the main namespace. |
|
||||||
To use it use nx.bipartite.reverse_havel_hakimi_graph |
|
||||||
""" |
|
||||||
G = nx.empty_graph(0, create_using, default=nx.MultiGraph) |
|
||||||
if G.is_directed(): |
|
||||||
raise nx.NetworkXError("Directed Graph not supported") |
|
||||||
|
|
||||||
# length of the each sequence |
|
||||||
lena = len(aseq) |
|
||||||
lenb = len(bseq) |
|
||||||
suma = sum(aseq) |
|
||||||
sumb = sum(bseq) |
|
||||||
|
|
||||||
if not suma == sumb: |
|
||||||
raise nx.NetworkXError( |
|
||||||
f"invalid degree sequences, sum(aseq)!=sum(bseq),{suma},{sumb}" |
|
||||||
) |
|
||||||
|
|
||||||
G = _add_nodes_with_bipartite_label(G, lena, lenb) |
|
||||||
|
|
||||||
if len(aseq) == 0 or max(aseq) == 0: |
|
||||||
return G # done if no edges |
|
||||||
|
|
||||||
# build list of degree-repeated vertex numbers |
|
||||||
astubs = [[aseq[v], v] for v in range(0, lena)] |
|
||||||
bstubs = [[bseq[v - lena], v] for v in range(lena, lena + lenb)] |
|
||||||
astubs.sort() |
|
||||||
bstubs.sort() |
|
||||||
while astubs: |
|
||||||
(degree, u) = astubs.pop() # take of largest degree node in the a set |
|
||||||
if degree == 0: |
|
||||||
break # done, all are zero |
|
||||||
# connect the source to the smallest degree nodes in the b set |
|
||||||
for target in bstubs[0:degree]: |
|
||||||
v = target[1] |
|
||||||
G.add_edge(u, v) |
|
||||||
target[0] -= 1 # note this updates bstubs too. |
|
||||||
if target[0] == 0: |
|
||||||
bstubs.remove(target) |
|
||||||
|
|
||||||
G.name = "bipartite_reverse_havel_hakimi_graph" |
|
||||||
return G |
|
||||||
|
|
||||||
|
|
||||||
def alternating_havel_hakimi_graph(aseq, bseq, create_using=None): |
|
||||||
"""Returns a bipartite graph from two given degree sequences using |
|
||||||
an alternating Havel-Hakimi style construction. |
|
||||||
|
|
||||||
The graph is composed of two partitions. Set A has nodes 0 to |
|
||||||
(len(aseq) - 1) and set B has nodes len(aseq) to (len(bseq) - 1). |
|
||||||
Nodes from the set A are connected to nodes in the set B by |
|
||||||
connecting the highest degree nodes in set A to alternatively the |
|
||||||
highest and the lowest degree nodes in set B until all stubs are |
|
||||||
connected. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
aseq : list |
|
||||||
Degree sequence for node set A. |
|
||||||
bseq : list |
|
||||||
Degree sequence for node set B. |
|
||||||
create_using : NetworkX graph instance, optional |
|
||||||
Return graph of this type. |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
The sum of the two sequences must be equal: sum(aseq)=sum(bseq) |
|
||||||
If no graph type is specified use MultiGraph with parallel edges. |
|
||||||
If you want a graph with no parallel edges use create_using=Graph() |
|
||||||
but then the resulting degree sequences might not be exact. |
|
||||||
|
|
||||||
The nodes are assigned the attribute 'bipartite' with the value 0 or 1 |
|
||||||
to indicate which bipartite set the node belongs to. |
|
||||||
|
|
||||||
This function is not imported in the main namespace. |
|
||||||
To use it use nx.bipartite.alternating_havel_hakimi_graph |
|
||||||
""" |
|
||||||
G = nx.empty_graph(0, create_using, default=nx.MultiGraph) |
|
||||||
if G.is_directed(): |
|
||||||
raise nx.NetworkXError("Directed Graph not supported") |
|
||||||
|
|
||||||
# length of the each sequence |
|
||||||
naseq = len(aseq) |
|
||||||
nbseq = len(bseq) |
|
||||||
suma = sum(aseq) |
|
||||||
sumb = sum(bseq) |
|
||||||
|
|
||||||
if not suma == sumb: |
|
||||||
raise nx.NetworkXError( |
|
||||||
f"invalid degree sequences, sum(aseq)!=sum(bseq),{suma},{sumb}" |
|
||||||
) |
|
||||||
|
|
||||||
G = _add_nodes_with_bipartite_label(G, naseq, nbseq) |
|
||||||
|
|
||||||
if len(aseq) == 0 or max(aseq) == 0: |
|
||||||
return G # done if no edges |
|
||||||
# build list of degree-repeated vertex numbers |
|
||||||
astubs = [[aseq[v], v] for v in range(0, naseq)] |
|
||||||
bstubs = [[bseq[v - naseq], v] for v in range(naseq, naseq + nbseq)] |
|
||||||
while astubs: |
|
||||||
astubs.sort() |
|
||||||
(degree, u) = astubs.pop() # take of largest degree node in the a set |
|
||||||
if degree == 0: |
|
||||||
break # done, all are zero |
|
||||||
bstubs.sort() |
|
||||||
small = bstubs[0 : degree // 2] # add these low degree targets |
|
||||||
large = bstubs[(-degree + degree // 2) :] # now high degree targets |
|
||||||
stubs = [x for z in zip(large, small) for x in z] # combine, sorry |
|
||||||
if len(stubs) < len(small) + len(large): # check for zip truncation |
|
||||||
stubs.append(large.pop()) |
|
||||||
for target in stubs: |
|
||||||
v = target[1] |
|
||||||
G.add_edge(u, v) |
|
||||||
target[0] -= 1 # note this updates bstubs too. |
|
||||||
if target[0] == 0: |
|
||||||
bstubs.remove(target) |
|
||||||
|
|
||||||
G.name = "bipartite_alternating_havel_hakimi_graph" |
|
||||||
return G |
|
||||||
|
|
||||||
|
|
||||||
@py_random_state(3) |
|
||||||
def preferential_attachment_graph(aseq, p, create_using=None, seed=None): |
|
||||||
"""Create a bipartite graph with a preferential attachment model from |
|
||||||
a given single degree sequence. |
|
||||||
|
|
||||||
The graph is composed of two partitions. Set A has nodes 0 to |
|
||||||
(len(aseq) - 1) and set B has nodes starting with node len(aseq). |
|
||||||
The number of nodes in set B is random. |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
aseq : list |
|
||||||
Degree sequence for node set A. |
|
||||||
p : float |
|
||||||
Probability that a new bottom node is added. |
|
||||||
create_using : NetworkX graph instance, optional |
|
||||||
Return graph of this type. |
|
||||||
seed : integer, random_state, or None (default) |
|
||||||
Indicator of random number generation state. |
|
||||||
See :ref:`Randomness<randomness>`. |
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] Guillaume, J.L. and Latapy, M., |
|
||||||
Bipartite graphs as models of complex networks. |
|
||||||
Physica A: Statistical Mechanics and its Applications, |
|
||||||
2006, 371(2), pp.795-813. |
|
||||||
.. [2] Jean-Loup Guillaume and Matthieu Latapy, |
|
||||||
Bipartite structure of all complex networks, |
|
||||||
Inf. Process. Lett. 90, 2004, pg. 215-221 |
|
||||||
https://doi.org/10.1016/j.ipl.2004.03.007 |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
The nodes are assigned the attribute 'bipartite' with the value 0 or 1 |
|
||||||
to indicate which bipartite set the node belongs to. |
|
||||||
|
|
||||||
This function is not imported in the main namespace. |
|
||||||
To use it use nx.bipartite.preferential_attachment_graph |
|
||||||
""" |
|
||||||
G = nx.empty_graph(0, create_using, default=nx.MultiGraph) |
|
||||||
if G.is_directed(): |
|
||||||
raise nx.NetworkXError("Directed Graph not supported") |
|
||||||
|
|
||||||
if p > 1: |
|
||||||
raise nx.NetworkXError(f"probability {p} > 1") |
|
||||||
|
|
||||||
naseq = len(aseq) |
|
||||||
G = _add_nodes_with_bipartite_label(G, naseq, 0) |
|
||||||
vv = [[v] * aseq[v] for v in range(0, naseq)] |
|
||||||
while vv: |
|
||||||
while vv[0]: |
|
||||||
source = vv[0][0] |
|
||||||
vv[0].remove(source) |
|
||||||
if seed.random() < p or len(G) == naseq: |
|
||||||
target = len(G) |
|
||||||
G.add_node(target, bipartite=1) |
|
||||||
G.add_edge(source, target) |
|
||||||
else: |
|
||||||
bb = [[b] * G.degree(b) for b in range(naseq, len(G))] |
|
||||||
# flatten the list of lists into a list. |
|
||||||
bbstubs = reduce(lambda x, y: x + y, bb) |
|
||||||
# choose preferentially a bottom node. |
|
||||||
target = seed.choice(bbstubs) |
|
||||||
G.add_node(target, bipartite=1) |
|
||||||
G.add_edge(source, target) |
|
||||||
vv.remove(vv[0]) |
|
||||||
G.name = "bipartite_preferential_attachment_model" |
|
||||||
return G |
|
||||||
|
|
||||||
|
|
||||||
@py_random_state(3) |
|
||||||
def random_graph(n, m, p, seed=None, directed=False): |
|
||||||
"""Returns a bipartite random graph. |
|
||||||
|
|
||||||
This is a bipartite version of the binomial (Erdős-Rényi) graph. |
|
||||||
The graph is composed of two partitions. Set A has nodes 0 to |
|
||||||
(n - 1) and set B has nodes n to (n + m - 1). |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
n : int |
|
||||||
The number of nodes in the first bipartite set. |
|
||||||
m : int |
|
||||||
The number of nodes in the second bipartite set. |
|
||||||
p : float |
|
||||||
Probability for edge creation. |
|
||||||
seed : integer, random_state, or None (default) |
|
||||||
Indicator of random number generation state. |
|
||||||
See :ref:`Randomness<randomness>`. |
|
||||||
directed : bool, optional (default=False) |
|
||||||
If True return a directed graph |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
The bipartite random graph algorithm chooses each of the n*m (undirected) |
|
||||||
or 2*nm (directed) possible edges with probability p. |
|
||||||
|
|
||||||
This algorithm is $O(n+m)$ where $m$ is the expected number of edges. |
|
||||||
|
|
||||||
The nodes are assigned the attribute 'bipartite' with the value 0 or 1 |
|
||||||
to indicate which bipartite set the node belongs to. |
|
||||||
|
|
||||||
This function is not imported in the main namespace. |
|
||||||
To use it use nx.bipartite.random_graph |
|
||||||
|
|
||||||
See Also |
|
||||||
-------- |
|
||||||
gnp_random_graph, configuration_model |
|
||||||
|
|
||||||
References |
|
||||||
---------- |
|
||||||
.. [1] Vladimir Batagelj and Ulrik Brandes, |
|
||||||
"Efficient generation of large random networks", |
|
||||||
Phys. Rev. E, 71, 036113, 2005. |
|
||||||
""" |
|
||||||
G = nx.Graph() |
|
||||||
G = _add_nodes_with_bipartite_label(G, n, m) |
|
||||||
if directed: |
|
||||||
G = nx.DiGraph(G) |
|
||||||
G.name = f"fast_gnp_random_graph({n},{m},{p})" |
|
||||||
|
|
||||||
if p <= 0: |
|
||||||
return G |
|
||||||
if p >= 1: |
|
||||||
return nx.complete_bipartite_graph(n, m) |
|
||||||
|
|
||||||
lp = math.log(1.0 - p) |
|
||||||
|
|
||||||
v = 0 |
|
||||||
w = -1 |
|
||||||
while v < n: |
|
||||||
lr = math.log(1.0 - seed.random()) |
|
||||||
w = w + 1 + int(lr / lp) |
|
||||||
while w >= m and v < n: |
|
||||||
w = w - m |
|
||||||
v = v + 1 |
|
||||||
if v < n: |
|
||||||
G.add_edge(v, n + w) |
|
||||||
|
|
||||||
if directed: |
|
||||||
# use the same algorithm to |
|
||||||
# add edges from the "m" to "n" set |
|
||||||
v = 0 |
|
||||||
w = -1 |
|
||||||
while v < n: |
|
||||||
lr = math.log(1.0 - seed.random()) |
|
||||||
w = w + 1 + int(lr / lp) |
|
||||||
while w >= m and v < n: |
|
||||||
w = w - m |
|
||||||
v = v + 1 |
|
||||||
if v < n: |
|
||||||
G.add_edge(n + w, v) |
|
||||||
|
|
||||||
return G |
|
||||||
|
|
||||||
|
|
||||||
@py_random_state(3) |
|
||||||
def gnmk_random_graph(n, m, k, seed=None, directed=False): |
|
||||||
"""Returns a random bipartite graph G_{n,m,k}. |
|
||||||
|
|
||||||
Produces a bipartite graph chosen randomly out of the set of all graphs |
|
||||||
with n top nodes, m bottom nodes, and k edges. |
|
||||||
The graph is composed of two sets of nodes. |
|
||||||
Set A has nodes 0 to (n - 1) and set B has nodes n to (n + m - 1). |
|
||||||
|
|
||||||
Parameters |
|
||||||
---------- |
|
||||||
n : int |
|
||||||
The number of nodes in the first bipartite set. |
|
||||||
m : int |
|
||||||
The number of nodes in the second bipartite set. |
|
||||||
k : int |
|
||||||
The number of edges |
|
||||||
seed : integer, random_state, or None (default) |
|
||||||
Indicator of random number generation state. |
|
||||||
See :ref:`Randomness<randomness>`. |
|
||||||
directed : bool, optional (default=False) |
|
||||||
If True return a directed graph |
|
||||||
|
|
||||||
Examples |
|
||||||
-------- |
|
||||||
from nx.algorithms import bipartite |
|
||||||
G = bipartite.gnmk_random_graph(10,20,50) |
|
||||||
|
|
||||||
See Also |
|
||||||
-------- |
|
||||||
gnm_random_graph |
|
||||||
|
|
||||||
Notes |
|
||||||
----- |
|
||||||
If k > m * n then a complete bipartite graph is returned. |
|
||||||
|
|
||||||
This graph is a bipartite version of the `G_{nm}` random graph model. |
|
||||||
|
|
||||||
The nodes are assigned the attribute 'bipartite' with the value 0 or 1 |
|
||||||
to indicate which bipartite set the node belongs to. |
|
||||||
|
|
||||||
This function is not imported in the main namespace. |
|
||||||
To use it use nx.bipartite.gnmk_random_graph |
|
||||||
""" |
|
||||||
G = nx.Graph() |
|
||||||
G = _add_nodes_with_bipartite_label(G, n, m) |
|
||||||
if directed: |
|
||||||
G = nx.DiGraph(G) |
|
||||||
G.name = f"bipartite_gnm_random_graph({n},{m},{k})" |
|
||||||
if n == 1 or m == 1: |
|
||||||
return G |
|
||||||
max_edges = n * m # max_edges for bipartite networks |
|
||||||
if k >= max_edges: # Maybe we should raise an exception here |
|
||||||
return nx.complete_bipartite_graph(n, m, create_using=G) |
|
||||||
|
|
||||||
top = [n for n, d in G.nodes(data=True) if d["bipartite"] == 0] |
|
||||||
bottom = list(set(G) - set(top)) |
|
||||||
edge_count = 0 |
|
||||||
while edge_count < k: |
|
||||||
# generate random edge,u,v |
|
||||||
u = seed.choice(top) |
|
||||||
v = seed.choice(bottom) |
|
||||||
if v in G[u]: |
|
||||||
continue |
|
||||||
else: |
|
||||||
G.add_edge(u, v) |
|
||||||
edge_count += 1 |
|
||||||
return G |
|
||||||
|
|
||||||
|
|
||||||
def _add_nodes_with_bipartite_label(G, lena, lenb): |
|
||||||
G.add_nodes_from(range(0, lena + lenb)) |
|
||||||
b = dict(zip(range(0, lena), [0] * lena)) |
|
||||||
b.update(dict(zip(range(lena, lena + lenb), [1] * lenb))) |
|
||||||
nx.set_node_attributes(G, b, "bipartite") |
|
||||||
return G |
|
Some files were not shown because too many files have changed in this diff Show More
Reference in new issue