studpy 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- studpy/__init__.py +2 -0
- studpy/batch/__init__.py +3 -0
- studpy/batch/batch_exec_cmd_iter.py +32 -0
- studpy/batch/validated_batch.py +25 -0
- studpy/params/__init__.py +5 -0
- studpy/params/configs.py +35 -0
- studpy/params/latin_hypercube_sampling.py +17 -0
- studpy/params/param.py +4 -0
- studpy/params/specs.py +18 -0
- studpy/study.py +9 -0
- studpy/targets/__init__.py +2 -0
- studpy/targets/one_file.py +16 -0
- studpy/targets/utils.py +5 -0
- studpy/utils/logger_print.py +6 -0
- studpy-0.1.0.dist-info/METADATA +20 -0
- studpy-0.1.0.dist-info/RECORD +17 -0
- studpy-0.1.0.dist-info/WHEEL +4 -0
studpy/__init__.py
ADDED
studpy/batch/__init__.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from abc import ABC,abstractmethod
|
|
3
|
+
from studpy.utils.logger_print import LoggerPrint
|
|
4
|
+
class BatchExecCmdIter(ABC):
|
|
5
|
+
logger=LoggerPrint();out_err_keywords:list[str]|None=None
|
|
6
|
+
@abstractmethod
|
|
7
|
+
def get_nb_runs(self):...
|
|
8
|
+
@abstractmethod
|
|
9
|
+
def cmd_callable(self,index_run):...
|
|
10
|
+
def pre_callable(A,index_run):0
|
|
11
|
+
def post_callable(A,index_run,returncode,stdout,stderr):0
|
|
12
|
+
def end_callable(A,nb_errors):0
|
|
13
|
+
def exec(A,max_parallel_jobs=5):
|
|
14
|
+
B=A.get_nb_runs();A.exec_nb_runs=B
|
|
15
|
+
if A.logger:A.logger.info(f"[{A.__class__.__name__}] START — {B} runs")
|
|
16
|
+
asyncio.run(A._main_batch(nb_runs=B,max_parallel_jobs=max_parallel_jobs))
|
|
17
|
+
async def _main_batch(A,nb_runs,max_parallel_jobs):
|
|
18
|
+
C=asyncio.Semaphore(max_parallel_jobs);D=[A._run_cmd(C,B)for B in range(nb_runs)];E=await asyncio.gather(*D);B=sum(1 for A in E if A);A.end_callable(B)
|
|
19
|
+
if A.logger:A.logger.info(f"[{A.__class__.__name__}] END — {B} errors / {A.exec_nb_runs} runs")
|
|
20
|
+
A.exec_nb_errors=B
|
|
21
|
+
async def _run_cmd(A,sem,index_run):
|
|
22
|
+
B=index_run;I=A.cmd_callable(B)
|
|
23
|
+
async with sem:
|
|
24
|
+
A.pre_callable(B)
|
|
25
|
+
if A.logger:A.logger.info(f"[{A.__class__.__name__}] RUN {B+1}/{A.exec_nb_runs}")
|
|
26
|
+
F=await asyncio.create_subprocess_shell(I,stdout=asyncio.subprocess.PIPE,stderr=asyncio.subprocess.PIPE);J,K=await F.communicate();C=F.returncode;G=J.decode().strip();H=K.decode().strip();D=None
|
|
27
|
+
if C!=0:D=f"return code {C} != 0"
|
|
28
|
+
elif A.out_err_keywords:
|
|
29
|
+
L=G.lower();M=H.lower()
|
|
30
|
+
for E in A.out_err_keywords:
|
|
31
|
+
if E.lower()in L or E.lower()in M:D=f"keyword '{E}' found in stdout/stderr";break
|
|
32
|
+
N=A.post_callable(B,C,G,H);return D or N
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import shutil
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from.batch_exec_cmd_iter import BatchExecCmdIter
|
|
6
|
+
class ValidatedBatchExecCmdIter(BatchExecCmdIter):
|
|
7
|
+
def __init__(A):A.failed_runs=[];A.success_runs=[]
|
|
8
|
+
@abstractmethod
|
|
9
|
+
def expected_output_path(self,index_run):...
|
|
10
|
+
def cleanup_path(A,index_run):0
|
|
11
|
+
def post_failure(E,index_run,returncode,stdout,stderr,reasons):
|
|
12
|
+
B=reasons;A=index_run;print(f" [FAIL] run {A} {' | '.join(B)}");C=stdout+stderr
|
|
13
|
+
if C.strip():
|
|
14
|
+
for D in C.strip().splitlines()[:15]:print(f" │ {D}")
|
|
15
|
+
return f"run {A} failed: {', '.join(B)}"
|
|
16
|
+
def post_callable(B,index_run,returncode,stdout,stderr):
|
|
17
|
+
A=index_run;C=B.expected_output_path(A);E=C.exists()and C.stat().st_size>0
|
|
18
|
+
if not E:
|
|
19
|
+
F=[f"missing/empty output: {C.name}"];D=B.cleanup_path(A)
|
|
20
|
+
if D and D.exists():shutil.rmtree(D)
|
|
21
|
+
B.failed_runs.append(A);return B.post_failure(A,returncode,stdout,stderr,F)
|
|
22
|
+
print(f" [OK] run {A} → {C.name}");B.success_runs.append(A)
|
|
23
|
+
def end_callable(A,nb_errors):
|
|
24
|
+
B=A.get_nb_runs();C=len(A.failed_runs)/B if B>0 else 0;print(f"\n {len(A.success_runs)} OK, {len(A.failed_runs)} failed ({C*100:.1f}%)")
|
|
25
|
+
if A.failed_runs:print(f" Failed indices: {A.failed_runs}")
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
from.configs import GaussianConfig,LHSConfig,LinearConfig,ListConfig,RandomIntConfig,TableConfig
|
|
2
|
+
from.latin_hypercube_sampling import LatinHypercubeSampling
|
|
3
|
+
from.param import Param
|
|
4
|
+
from.specs import ParamsSpecs
|
|
5
|
+
__all__=['ParamsSpecs','Param','GaussianConfig','LinearConfig','ListConfig','LHSConfig','RandomIntConfig','TableConfig','LatinHypercubeSampling']
|
studpy/params/configs.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
_D='linear'
|
|
2
|
+
_C='gaussian'
|
|
3
|
+
_B='random_int'
|
|
4
|
+
_A=None
|
|
5
|
+
from typing import Any,Literal
|
|
6
|
+
import numpy as np
|
|
7
|
+
from pandas import DataFrame
|
|
8
|
+
from pydantic import BaseModel,model_validator
|
|
9
|
+
class LHSConfig(BaseModel):type:Literal['lhs']='lhs';lhs_reference:str;low:float;high:float
|
|
10
|
+
class TableConfig(BaseModel):type:Literal['table']='table';table_reference:str;col:str
|
|
11
|
+
class RandomIntConfig(BaseModel):
|
|
12
|
+
type:Literal[_B]=_B;low:int;high:int;size:int
|
|
13
|
+
def get_size(A):return A.size
|
|
14
|
+
def get_dataframe(A,col_name='value'):B=np.random.randint(A.low,A.high+1,size=A.size);return DataFrame(B,columns=[col_name])
|
|
15
|
+
class GaussianConfig(BaseModel):
|
|
16
|
+
type:Literal[_C]=_C;mean:float;std:float;size:int;low:float|_A=_A;high:float|_A=_A;seed:int|_A=_A
|
|
17
|
+
def get_size(A):return A.size
|
|
18
|
+
def get_value(A,index):return .0
|
|
19
|
+
class ListConfig(BaseModel):
|
|
20
|
+
type:Literal['list']='list';values:list[Any]
|
|
21
|
+
def get_size(A):return len(A.values)
|
|
22
|
+
def get_value(A,index):return A.values[index]
|
|
23
|
+
class LinearConfig(BaseModel):
|
|
24
|
+
type:Literal[_D]=_D;size:int|_A=_A;low:float;high:float;step_value:float|_A=_A
|
|
25
|
+
@model_validator(mode='before')
|
|
26
|
+
@classmethod
|
|
27
|
+
def resolve_missing_field(cls,values):
|
|
28
|
+
G='step_value';F='size';A=values;B=A.get(F);D=A.get('low');E=A.get('high');C=A.get(G)
|
|
29
|
+
if B is _A and C is _A:raise ValueError("'size' or 'step_value' should be defined")
|
|
30
|
+
if B is not _A and C is not _A:raise ValueError("only 'size' or 'step_value' should be defined")
|
|
31
|
+
if C is _A:A[G]=(E-D)/(B-1)
|
|
32
|
+
if B is _A:A[F]=round((E-D)/C)+1
|
|
33
|
+
return A
|
|
34
|
+
def get_size(A):return A.size
|
|
35
|
+
def get_dataframe(A,col_name='value'):B=np.arange(A.low,A.high+A.step_value,A.step_value)[:A.size];return DataFrame(B,columns=[col_name])
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from pandas import DataFrame
|
|
3
|
+
from.configs import LHSConfig
|
|
4
|
+
from.param import Param
|
|
5
|
+
class LatinHypercubeSampling:
|
|
6
|
+
def __init__(A,n_samples,random_seed=42):A.n_samples=n_samples;A.random_seed=random_seed
|
|
7
|
+
def build_cases(D,parameters,col_names):
|
|
8
|
+
E=parameters;B=D.n_samples;F=random.Random(D.random_seed);G={}
|
|
9
|
+
for A in E:H=list(range(B));F.shuffle(H);G[A.target]=H
|
|
10
|
+
I=[]
|
|
11
|
+
for K in range(B):
|
|
12
|
+
J={}
|
|
13
|
+
for A in E:
|
|
14
|
+
if not isinstance(A.config,LHSConfig):raise Exception('parameter should be LHSConfig')
|
|
15
|
+
C=A.config;L=G[A.target][K];M=(L+F.random())/B;N=col_names[A.target];J[N]=round(C.low+M*(C.high-C.low),6)
|
|
16
|
+
I.append(J)
|
|
17
|
+
return DataFrame(I)
|
studpy/params/param.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
from typing import Annotated
|
|
2
|
+
from pydantic import BaseModel,Field
|
|
3
|
+
from.configs import GaussianConfig,LHSConfig,LinearConfig,ListConfig,RandomIntConfig,TableConfig
|
|
4
|
+
class Param(BaseModel):target:str;config:Annotated[GaussianConfig|LinearConfig|ListConfig|LHSConfig|RandomIntConfig|TableConfig,Field(discriminator='type')];name:str|None=None
|
studpy/params/specs.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
_D='other'
|
|
2
|
+
_C=None
|
|
3
|
+
_B='table'
|
|
4
|
+
_A='lhs'
|
|
5
|
+
import math
|
|
6
|
+
from pandas import DataFrame
|
|
7
|
+
from.latin_hypercube_sampling import LatinHypercubeSampling
|
|
8
|
+
from.param import Param
|
|
9
|
+
NONAME_PREFIX='Col '
|
|
10
|
+
class ParamsSpecs:
|
|
11
|
+
def __init__(A,parameters,tables=_C,lhs=_C):A.parameters=parameters;A.tables=tables;A.lhs=lhs;A._params_by_category={_B:[A for A in A.parameters if A.config.type==_B],_A:[A for A in A.parameters if A.config.type==_A],_D:[A for A in A.parameters if A.config.type not in[_B,_A]]};A._references={_B:list(set([A.config.table_reference for A in A._params_by_category[_B]])),_A:list(set([A.config.lhs_reference for A in A._params_by_category[_A]]))}
|
|
12
|
+
def cases_count(A):return math.prod([A.tables[B].shape[0]for B in A._references[_B]]+[A.lhs[B].n_samples for B in A._references[_A]]+[A.config.get_size()for A in A._params_by_category[_D]])
|
|
13
|
+
def params_targets_names(A):return{A.target:A.name or f"{NONAME_PREFIX}{B}"for(B,A)in enumerate(A.parameters)}
|
|
14
|
+
def build_cases(B):
|
|
15
|
+
G='cross';D=B.params_targets_names();A=_C
|
|
16
|
+
for E in B._references[_A]:H=[A for A in B._params_by_category[_A]if A.config.lhs_reference==E];C=B.lhs[E].build_cases(H,D);A=C if A is _C else A.merge(C,how=G)
|
|
17
|
+
for F in B._params_by_category[_D]:C=F.config.get_dataframe(D[F.target]);A=C if A is _C else A.merge(C,how=G)
|
|
18
|
+
return A
|
studpy/study.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from.params import ParamsSpecs
|
|
3
|
+
from.targets import TargetsOneFile
|
|
4
|
+
class Study:
|
|
5
|
+
def __init__(A,specs,targets):B=specs;A.specs=B;A.cases=B.build_cases();A.targets=targets
|
|
6
|
+
def cases_count(A):return len(A.cases)
|
|
7
|
+
def cases_save(A,p):A.cases.to_csv(p,index=False)
|
|
8
|
+
def case_targets_values(A,case_index):B=A.cases.iloc[case_index].to_dict();C=A.specs.params_targets_names();return{A:B[C]for(A,C)in C.items()}
|
|
9
|
+
def case_build_inputs(A,case_index):B=case_index;C=A.case_targets_values(B);A.targets.build(case_index=B,case_targets_values=C)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import json,shutil
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any
|
|
4
|
+
from.utils import dict_set_nested
|
|
5
|
+
class TargetsOneFile:
|
|
6
|
+
def __init__(A,output_folder,template_file,subfoldername=None):A.output_folder=output_folder;A.template_file=template_file;A.subfoldername=subfoldername
|
|
7
|
+
def build(A,case_index,case_targets_values):
|
|
8
|
+
D=A.template_file.name;C=A.output_folder/f"{case_index}";C.mkdir()
|
|
9
|
+
if A.subfoldername:(C/A.subfoldername).mkdir();B=C/A.subfoldername/D
|
|
10
|
+
else:B=C/D
|
|
11
|
+
shutil.copy(A.template_file,B);F=B.suffix.lower()
|
|
12
|
+
if F=='.json':
|
|
13
|
+
E=json.loads(B.read_text())
|
|
14
|
+
for(G,H)in case_targets_values.items():dict_set_nested(E,G,H)
|
|
15
|
+
I=json.dumps(E);B.write_text(I)
|
|
16
|
+
else:raise Exception('template type not known')
|
studpy/targets/utils.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: studpy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A collection of useful functionalities for scientific studies.
|
|
5
|
+
Author-email: PinkSalmon <dev@pinksalmon.cloud>
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Requires-Dist: pandas
|
|
12
|
+
Requires-Dist: pydantic
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
15
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
16
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
17
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
18
|
+
Provides-Extra: docs
|
|
19
|
+
Requires-Dist: mkdocstrings-python; extra == 'docs'
|
|
20
|
+
Requires-Dist: zensical; extra == 'docs'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
studpy/__init__.py,sha256=YBs_bmmbPMsmLqtDHqNvqqI2Z2JdghQOhMtfCc76dt8,41
|
|
2
|
+
studpy/study.py,sha256=NsLsLS8M_7RPk4GU526phkrhJYuF8itDmdML4dYtbFE,558
|
|
3
|
+
studpy/batch/__init__.py,sha256=N2tl5q3lPuNLPQskrLZWEPDLlvkM8VvEV5Wcj8e-B0U,159
|
|
4
|
+
studpy/batch/batch_exec_cmd_iter.py,sha256=Pd28LcHZ1q0pb2Vx7ISgE2Ao7q5Qs5ml0zn5a9tAMEw,1613
|
|
5
|
+
studpy/batch/validated_batch.py,sha256=etMHSeikOIwp-2UqC_bt0CKEZ45HT7AcTsu7o11ej-Q,1251
|
|
6
|
+
studpy/params/__init__.py,sha256=xi70KbcvDWpKJMUFE8TSk-1QG3ODms-DEPQmEOhn-18,356
|
|
7
|
+
studpy/params/configs.py,sha256=2OItCi6SdNhaMrPdAk4qF-gfYPih3CMlUq7oKHfc32k,1666
|
|
8
|
+
studpy/params/latin_hypercube_sampling.py,sha256=1QqZ8IgIYW2O0uhoTxf_11B61mnujkXLb64AS0p6Q4Q,669
|
|
9
|
+
studpy/params/param.py,sha256=63PtPXQQx6LHvcGL7Y8uCLltcpJrfgRa91qjjubwbbs,338
|
|
10
|
+
studpy/params/specs.py,sha256=XaoABLufpTiAFYIJtzwCaFYa-G9wMeTzeUWdx7Eacn4,1302
|
|
11
|
+
studpy/targets/__init__.py,sha256=s-SxrZIIhJp4b0NxPQmhVWWTxSUeNO4HCpg8GS0pcII,62
|
|
12
|
+
studpy/targets/one_file.py,sha256=_gXd7wIoF6x7dsvIXHE5oZo6Tw3EnlANZENr-6Z0HxY,725
|
|
13
|
+
studpy/targets/utils.py,sha256=7w3zEfu7hQZZFq1zHy7gfhysbCl0on_w1l3A9R_eXMs,150
|
|
14
|
+
studpy/utils/logger_print.py,sha256=9LNtL61ind3P54i-c2vgi2oh-xHLR4pUOXCkV6MISag,211
|
|
15
|
+
studpy-0.1.0.dist-info/METADATA,sha256=495Yx-87_EO1HSdRPM_S78wKX70LlAd7Wmp1yyAk5_A,747
|
|
16
|
+
studpy-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
17
|
+
studpy-0.1.0.dist-info/RECORD,,
|