From c9fa1627ba3e52de418fa54bcabdf594b74b4d2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wagner=20Gon=C3=A7alves=20Pinto?= <255828055+wagnerjpinto@users.noreply.github.com> Date: Tue, 28 Apr 2026 09:37:27 +0200 Subject: [PATCH 01/17] ignore venv .bemol --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e9a6557..eef9504 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ **/__pycache__/ -**/.pytest_cache/ \ No newline at end of file +**/.pytest_cache/ + +/.bemol/ \ No newline at end of file From 0575d909761f33d453739f1c61acf3c41eb676bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wagner=20Gon=C3=A7alves=20Pinto?= <255828055+wagnerjpinto@users.noreply.github.com> Date: Tue, 28 Apr 2026 09:57:55 +0200 Subject: [PATCH 02/17] add venv + pip requirements and instructions to readme --- README.md | 23 +++++++++++++++++------ requirements.txt | 31 +++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 requirements.txt diff --git a/README.md b/README.md index d7e7dd4..f869785 100644 --- a/README.md +++ b/README.md @@ -81,14 +81,25 @@ The library is based on [Numpy](https://numpy.org/), [Scipy](https://scipy.org/) [pytest](https://pytest.org/) is as well optional, used for the evaluation of the tests inside the `tests` sub-folder. -The list of considered/tested versions are available in environment.yaml. -For use with conda: +The list of considered/tested versions are available in conda and pip formats. -```bash -conda env create -f environment.yaml -``` +- For use with conda: + + ```bash + conda env create -f environment.yaml + ``` + + Use `conda activate bemol` to use the env. + +- Using pip and venv: + + ```bash + python3 -m venv .bemol + source .bemol/bin/activate + pip install -r requirements.txt + ``` -Use `conda activate bemol` to use the env. + Use `source .bemol/bin/activate` to use the env. ## Authors diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..131ec14 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,31 @@ +colorama==0.4.6 +contourpy==1.3.3 +cycler==0.12.1 +exceptiongroup==1.3.1 +fonttools==4.62.1 +iniconfig==2.3.0 +kiwisolver==1.5.0 +matplotlib==3.10.1 +munkres==1.1.4 +numpy==1.26.4 +packaging==26.2 +pandas==3.0.2 +pillow==12.2.0 +pip==26.0.1 +pluggy==1.6.0 +pyparsing==3.3.2 +PySide6==6.11.0 +pytest==8.3.4 +python-dateutil==2.9.0.post0 +pytz==2026.1.post1 +PyYAML==6.0.2 +scipy==1.14.1 +setuptools==82.0.1 +shiboken6==6.11.0 +six==1.17.0 +tomli==2.4.1 +tornado==6.5.5 +typing_extensions==4.15.0 +tzdata==2026.2 +unicodedata2==17.0.1 +wheel==0.47.0 From fdc599198a4faac4c03072c0a6925d586e77382c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wagner=20Gon=C3=A7alves=20Pinto?= <255828055+wagnerjpinto@users.noreply.github.com> Date: Tue, 28 Apr 2026 09:58:42 +0200 Subject: [PATCH 03/17] move secondary models to independent files, rename modules --- bemol/bem.py | 53 +------ bemol/correction.py | 55 +++++++ bemol/secondary.py | 202 -------------------------- bemol/secondary/__init__.py | 7 + bemol/secondary/dynamicInflow.py | 36 +++++ bemol/secondary/hubTipLoss.py | 42 ++++++ bemol/secondary/skewAngle.py | 21 +++ bemol/secondary/turbulentWakeState.py | 44 ++++++ bemol/secondary/yawModel.py | 61 ++++++++ bemol/tools.py | 5 +- environment.yaml | 2 +- samples/aligned.py | 10 +- samples/iea15mw.py | 4 +- samples/pitch_maneuver.py | 10 +- samples/yaw.py | 6 +- samples/yaw_models.py | 8 +- test.py | 12 ++ tests/test_result.py | 5 +- tests/test_unit.py | 82 +++++++---- 19 files changed, 361 insertions(+), 304 deletions(-) create mode 100644 bemol/correction.py delete mode 100644 bemol/secondary.py create mode 100644 bemol/secondary/__init__.py create mode 100644 bemol/secondary/dynamicInflow.py create mode 100644 bemol/secondary/hubTipLoss.py create mode 100644 bemol/secondary/skewAngle.py create mode 100644 bemol/secondary/turbulentWakeState.py create mode 100644 bemol/secondary/yawModel.py create mode 100644 test.py diff --git a/bemol/bem.py b/bemol/bem.py index 7e79f32..fe62ae3 100644 --- a/bemol/bem.py +++ b/bemol/bem.py @@ -5,60 +5,11 @@ import numpy as np from . import rotor -from . import secondary +from . import correction from . import tools - -class Corrections(object): - """Class for storing corrections. - - Consider all the corrections available in the secondary - models module. If not given by the user considers the base (empty) - correction with the default parameters. - - Parameters - ---------- - corrections : optional - dictionary or list with the secondary corrections, either classes of - instances - in case of custom parameters. If dictionary is - given, the key must be the name of the correction as defined in - secondary.py with a lower first letter. - - """ - def __init__(self,corrections:dict={}): - for name, obj in inspect.getmembers(secondary): - if inspect.isclass(obj): - _name = name[0].lower() + name[1:] - if type(corrections) is dict: - if _name in corrections: - # instantiation of correction with default values - corr = corrections[_name] - corr = corr() if isinstance(corr,type) else corr - setattr(self,_name,corr) - else: - setattr(self,_name,obj.Dummy()) - else: - effect_name = obj.__qualname__ - setattr(self,_name,obj.Dummy()) - # loop for all effects to check if any of the input - # corrections are inner of the available corrections - for corr in corrections: - # instantiation of correction with default values - corr = corr() if isinstance(corr,type) else corr - correction_name = type(corr).__qualname__ - if effect_name in correction_name: - setattr(self,_name,corr) - break - - - def __iter__(self): - """Iterate corrections.""" - for value in self.__dict__.values(): - yield value - - class BaseBEM: """Base BEM class. @@ -89,7 +40,7 @@ def __init__(self,rotor:rotor.Rotor,rho:float=1.225,corrections:dict=None): self.n = len(rotor.sections) if corrections is None: corrections = {} - self.corrections = Corrections(corrections) + self.corrections = correction.Corrections(corrections) diff --git a/bemol/correction.py b/bemol/correction.py new file mode 100644 index 0000000..dba8939 --- /dev/null +++ b/bemol/correction.py @@ -0,0 +1,55 @@ + +import inspect +from types import ModuleType + +from . import secondary + + +class Corrections(object): + """Class for storing corrections. + + Consider all the corrections available in the secondary + models module. If not given by the user considers the base (empty) + correction with the default parameters. + + Parameters + ---------- + corrections : optional + dictionary or list with the secondary corrections, either classes of + instances - in case of custom parameters. If dictionary is + given, the key must be the name of the correction as defined in + secondary.py with a lower first letter. + + """ + def __init__(self,corrections:dict={}): + + for name_mod, mod in inspect.getmembers(secondary): + if not isinstance(mod,ModuleType): continue + effect_name = name_mod[0].lower() + name_mod[1:] + for name_obj, obj in inspect.getmembers(mod): + if inspect.isclass(obj): + if type(corrections) is dict: + if effect_name in corrections: + # instantiation of correction with default values + corr = corrections[effect_name] + corr = corr() if isinstance(corr,type) else corr + setattr(self,effect_name,corr) + else: + setattr(self,effect_name,mod.Dummy()) + else: + setattr(self,effect_name,mod.Dummy()) + # loop for all effects to check if any of the input + # corrections are classes of the available corrections + for corr in corrections: + # instantiation of correction with default values + # TODO: check name before instanciating! + corr = corr() if isinstance(corr,type) else corr + if effect_name in corr.__module__: + setattr(self,effect_name,corr) + break + + + def __iter__(self): + """Iterate corrections.""" + for value in self.__dict__.values(): + yield value diff --git a/bemol/secondary.py b/bemol/secondary.py deleted file mode 100644 index 71bdc64..0000000 --- a/bemol/secondary.py +++ /dev/null @@ -1,202 +0,0 @@ -""" - -Secondary correction models - -""" - -import numpy as np -from . import bem - - -INV_TWO_PI = float(2.0/np.pi) - - -class HubTipLoss: - - class Dummy: - """Empty hub/tip loss correction model.""" - - def __init__(self) -> None: - return - - def __call__(self,*args,**kwargs): - return 1.0 - - class Prandtl: - """Prandt hub/tip loss correction model. - - Parameters - ---------- - epsilon: float, optional - minimal value of F to avoid errors close to/at the bounds of the - blade. Default is 1e-12. - - """ - def __init__(self,epsilon=1e-12) -> None: - self._epsilon = epsilon - return - - def __call__(self,radius,nBlades,hubRadius,tipRadius,inflowAngle): - - fTip = nBlades/2.0*((tipRadius - radius) / (radius*np.abs(np.sin(inflowAngle)))) - fTip = INV_TWO_PI*np.arccos(np.exp(-fTip)) - - fRoot = nBlades/2.0*((radius - hubRadius) / (hubRadius*np.abs(np.sin(inflowAngle)))) - fRoot = INV_TWO_PI*np.arccos(np.exp(-fRoot)) - - return max(fTip*fRoot,self._epsilon) - - -class SkewAngle: - - class Dummy: - """Empty skewed wake model.""" - - def __init__(self) -> None: - return - - def __call__(self,axialInduction,*args,**kwargs): - return axialInduction - - class Burton: - """Burton skewed wake model.""" - - def __init__(self,) -> None: - return - - def __call__(self,axialInduction,yawAngle): - return (0.6 * axialInduction + 1.0) * yawAngle - - -class DynamicInflow: - - class Dummy: - """Empty dynamic inflow model.""" - - def __init__(self) -> None: - return - - def __call__(self,axialInduction,*args,**kwargs): - return axialInduction - - class Knudsen: - """Knudsen dynamic inflow model.""" - - def __init__(self,alphaDynamic=0.3,tauScale=3.0) -> None: - self._alpha0 = alphaDynamic - self.alphaDynamic = alphaDynamic - self.tauScale = tauScale - - def __call__(self,axialInduction,Ux,radius,tStep): - if (tStep == 0.): - raise ValueError( - f'Using a dynamic inflow model with tStep = {tStep} does not make sense!' - ) - # lower bound for wind velocity - kappa = 1. / (self.tauScale * radius / max(1.0,Ux) ) - alphaDynamic = tStep*kappa*(axialInduction - self.alphaDynamic) + self.alphaDynamic - self.alphaDynamic = alphaDynamic - - return alphaDynamic - - def restart(self): - """Restart alpha to the starting value.""" - self.alphaDynamic = self._alpha0 - - -class YawModel: - - class Dummy: - """Empty skewed wake correction model.""" - - def __init__(self) -> None: - return - - def __call__(self,axialInduction,*args,**kwargs): - return axialInduction - - class PittAndPeters: - """Pitt & Peters skewed wake model. - - Pitt & Peters 1980, Theoretical prediction of dynamic-inflow derivatives. - 6th European Rotorcraft & Powered Lift Aircraft Forum - - Snel & Schepers 1995, Joint investigation of dynamic inflow effects and - implementation of an engineering method. Technical Report ECN-C–94-107, - Energy Research Centre of the Netherlands (ECN), - publications.ecn.nl/E/1995/ECN-C--94-107 - - """ - - def __init__(self,factor=15.*np.pi/64.) -> None: - self.factor = factor - - def __call__(self,axialInduction,wakeSkewAngle,azimuthAngle,radius,hubRadius,tipRadius): - return axialInduction*( - 1. + self.factor*np.tan(wakeSkewAngle/2.)*radius/tipRadius*np.sin(azimuthAngle) - ) - - class IFPEN: - """IFPEN skewed wake model. - - Blondel et al. 2017, Improving a BEM yaw model based on NewMexico - experimental data and Vortex/CFD simulations. - CFM 2017 - 23ème Congrès Français de Mécanique - ifp.hal.science/hal-01663643 - - """ - - def __init__(self,A0=0.35,Phi1=-np.pi/9,Phi2=np.pi) -> None: - self.A0 = A0 - self.Phi1 = Phi1 - self.Phi2 = Phi2 - - def __call__(self,axialInduction,wakeSkewAngle,azimuthAngle,radius,hubRadius,tipRadius): - k1 = (1 - self.A0) + self.A0*(radius - hubRadius)/(tipRadius - hubRadius) - k2 = 1 - self.A0*(radius - hubRadius)/(tipRadius - hubRadius) - eta = radius/tipRadius - return axialInduction*( - 1. \ - + k1*eta*np.tan(wakeSkewAngle/2.)*np.sin(azimuthAngle + self.Phi1) \ - + k2*(1 - eta)*np.tan(wakeSkewAngle/2.)*np.sin(azimuthAngle + self.Phi2) - ) - - -class TurbulentWakeState: - - class Dummy: - """Empty high-induction correction model. - - Calls the base thrust coefficient calculation method. - """ - - def __init__(self) -> None: - return - - def __call__(self,axialInduction,lossFactor,skewAngle,*args,**kwargs): - return bem.BaseBEM.CT(axialInduction,lossFactor,skewAngle) - - - class Buhl: - """Buhl's empirical high-induction correction model. - - TODO: need to find the ref to this method. - - """ - - def __init__(self,beta:float=0.4,da:float=0.02) -> None: - self.beta = beta - self.da = da - - def __call__(self,axialInduction,lossFactor,skewAngle): - f0 = bem.BaseBEM.CT(self.beta,lossFactor,skewAngle) - fp0 = ( - bem.BaseBEM.CT(self.beta + self.da,lossFactor,skewAngle) - - bem.BaseBEM.CT(self.beta - self.da,lossFactor,skewAngle) - ) / (2.*self.da) - f1 = 2.*np.cos(skewAngle) - k2 = (f1-f0-fp0*(1.0 - self.beta)) / (1.0 - self.beta)**2. - k1 = fp0 - 2.*k2*self.beta - k0 = f1 - k1 - k2 - - return k0 + k1*axialInduction + k2*axialInduction**2. \ No newline at end of file diff --git a/bemol/secondary/__init__.py b/bemol/secondary/__init__.py new file mode 100644 index 0000000..e4973cf --- /dev/null +++ b/bemol/secondary/__init__.py @@ -0,0 +1,7 @@ + + +from . import dynamicInflow +from . import hubTipLoss +from . import skewAngle +from . import turbulentWakeState +from . import yawModel diff --git a/bemol/secondary/dynamicInflow.py b/bemol/secondary/dynamicInflow.py new file mode 100644 index 0000000..8d4a72f --- /dev/null +++ b/bemol/secondary/dynamicInflow.py @@ -0,0 +1,36 @@ + + + +class Dummy: + """Empty dynamic inflow model.""" + + def __init__(self) -> None: + return + + def __call__(self,axialInduction,*args,**kwargs): + return axialInduction + + +class Knudsen: + """Knudsen dynamic inflow model.""" + + def __init__(self,alphaDynamic=0.3,tauScale=3.0) -> None: + self._alpha0 = alphaDynamic + self.alphaDynamic = alphaDynamic + self.tauScale = tauScale + + def __call__(self,axialInduction,Ux,radius,tStep): + if (tStep == 0.): + raise ValueError( + f'Using a dynamic inflow model with tStep = {tStep} does not make sense!' + ) + # lower bound for wind velocity + kappa = 1. / (self.tauScale * radius / max(1.0,Ux) ) + alphaDynamic = tStep*kappa*(axialInduction - self.alphaDynamic) + self.alphaDynamic + self.alphaDynamic = alphaDynamic + + return alphaDynamic + + def restart(self): + """Restart alpha to the starting value.""" + self.alphaDynamic = self._alpha0 \ No newline at end of file diff --git a/bemol/secondary/hubTipLoss.py b/bemol/secondary/hubTipLoss.py new file mode 100644 index 0000000..6c3a602 --- /dev/null +++ b/bemol/secondary/hubTipLoss.py @@ -0,0 +1,42 @@ + + +import numpy as np + +INV_TWO_PI = float(2.0/np.pi) + + + +class Dummy: + """Empty hub/tip loss correction model.""" + + def __init__(self) -> None: + return + + def __call__(self,*args,**kwargs): + return 1.0 + + +class Prandtl: + """Prandt hub/tip loss correction model. + + Parameters + ---------- + epsilon: float, optional + minimal value of F to avoid errors close to/at the bounds of the + blade. Default is 1e-12. + + """ + def __init__(self,epsilon=1e-12) -> None: + self._epsilon = epsilon + return + + def __call__(self,radius,nBlades,hubRadius,tipRadius,inflowAngle): + + fTip = nBlades/2.0*((tipRadius - radius) / (radius*np.abs(np.sin(inflowAngle)))) + fTip = INV_TWO_PI*np.arccos(np.exp(-fTip)) + + fRoot = nBlades/2.0*((radius - hubRadius) / (hubRadius*np.abs(np.sin(inflowAngle)))) + fRoot = INV_TWO_PI*np.arccos(np.exp(-fRoot)) + + return max(fTip*fRoot,self._epsilon) + diff --git a/bemol/secondary/skewAngle.py b/bemol/secondary/skewAngle.py new file mode 100644 index 0000000..93f6ff4 --- /dev/null +++ b/bemol/secondary/skewAngle.py @@ -0,0 +1,21 @@ + + +class Dummy: + """Empty skewed wake model.""" + + def __init__(self) -> None: + return + + def __call__(self,axialInduction,*args,**kwargs): + return axialInduction + + +class Burton: + """Burton skewed wake model.""" + + def __init__(self,) -> None: + return + + def __call__(self,axialInduction,yawAngle): + return (0.6 * axialInduction + 1.0) * yawAngle + diff --git a/bemol/secondary/turbulentWakeState.py b/bemol/secondary/turbulentWakeState.py new file mode 100644 index 0000000..9a5edcc --- /dev/null +++ b/bemol/secondary/turbulentWakeState.py @@ -0,0 +1,44 @@ + + +import numpy as np +from .. import bem + + + + +class Dummy: + """Empty high-induction correction model. + + Calls the base thrust coefficient calculation method. + """ + + def __init__(self) -> None: + return + + def __call__(self,axialInduction,lossFactor,skewAngle,*args,**kwargs): + return bem.BaseBEM.CT(axialInduction,lossFactor,skewAngle) + + +class Buhl: + """Buhl's empirical high-induction correction model. + + https://docs.nlr.gov/docs/fy05osti/36834.pdf + + """ + + def __init__(self,beta:float=0.4,da:float=0.02) -> None: + self.beta = beta + self.da = da + + def __call__(self,axialInduction,lossFactor,skewAngle): + f0 = bem.BaseBEM.CT(self.beta,lossFactor,skewAngle) + fp0 = ( + bem.BaseBEM.CT(self.beta + self.da,lossFactor,skewAngle) + - bem.BaseBEM.CT(self.beta - self.da,lossFactor,skewAngle) + ) / (2.*self.da) + f1 = 2.*np.cos(skewAngle) + k2 = (f1-f0-fp0*(1.0 - self.beta)) / (1.0 - self.beta)**2. + k1 = fp0 - 2.*k2*self.beta + k0 = f1 - k1 - k2 + + return k0 + k1*axialInduction + k2*axialInduction**2. \ No newline at end of file diff --git a/bemol/secondary/yawModel.py b/bemol/secondary/yawModel.py new file mode 100644 index 0000000..a585202 --- /dev/null +++ b/bemol/secondary/yawModel.py @@ -0,0 +1,61 @@ + +import numpy as np + + + + +class Dummy: + """Empty skewed wake correction model.""" + + def __init__(self) -> None: + return + + def __call__(self,axialInduction,*args,**kwargs): + return axialInduction + + +class PittAndPeters: + """Pitt & Peters skewed wake model. + + Pitt & Peters 1980, Theoretical prediction of dynamic-inflow derivatives. + 6th European Rotorcraft & Powered Lift Aircraft Forum + + Snel & Schepers 1995, Joint investigation of dynamic inflow effects and + implementation of an engineering method. Technical Report ECN-C–94-107, + Energy Research Centre of the Netherlands (ECN), + publications.ecn.nl/E/1995/ECN-C--94-107 + + """ + + def __init__(self,factor=15.*np.pi/64.) -> None: + self.factor = factor + + def __call__(self,axialInduction,wakeSkewAngle,azimuthAngle,radius,hubRadius,tipRadius): + return axialInduction*( + 1. + self.factor*np.tan(wakeSkewAngle/2.)*radius/tipRadius*np.sin(azimuthAngle) + ) + +class IFPEN: + """IFPEN skewed wake model. + + Blondel et al. 2017, Improving a BEM yaw model based on NewMexico + experimental data and Vortex/CFD simulations. + CFM 2017 - 23ème Congrès Français de Mécanique + ifp.hal.science/hal-01663643 + + """ + + def __init__(self,A0=0.35,Phi1=-np.pi/9,Phi2=np.pi) -> None: + self.A0 = A0 + self.Phi1 = Phi1 + self.Phi2 = Phi2 + + def __call__(self,axialInduction,wakeSkewAngle,azimuthAngle,radius,hubRadius,tipRadius): + k1 = (1 - self.A0) + self.A0*(radius - hubRadius)/(tipRadius - hubRadius) + k2 = 1 - self.A0*(radius - hubRadius)/(tipRadius - hubRadius) + eta = radius/tipRadius + return axialInduction*( + 1. \ + + k1*eta*np.tan(wakeSkewAngle/2.)*np.sin(azimuthAngle + self.Phi1) \ + + k2*(1 - eta)*np.tan(wakeSkewAngle/2.)*np.sin(azimuthAngle + self.Phi2) + ) diff --git a/bemol/tools.py b/bemol/tools.py index 3fdad23..6976db8 100644 --- a/bemol/tools.py +++ b/bemol/tools.py @@ -1,7 +1,10 @@ import numpy as np -def calculateVelocity(wind:float,omega:float,rad:float,azi:float,yaw:float,tilt:float,precone:float): +def calculateVelocity( + wind:float,omega:float,rad:float,azi:float, + yaw:float,tilt:float,precone:float + ): """Calculate relative velocity for a given wind configuration Parameters diff --git a/environment.yaml b/environment.yaml index 03cdfdf..d457504 100644 --- a/environment.yaml +++ b/environment.yaml @@ -5,7 +5,7 @@ dependencies: - python==3.11.2 - numpy==1.26.4 - scipy==1.14.1 - - pandas==2.2.1 + - pandas==3.0.2 - pyyaml==6.0.2 - matplotlib==3.10.1 - pytest==8.3.4 \ No newline at end of file diff --git a/samples/aligned.py b/samples/aligned.py index 5314b05..72b4163 100644 --- a/samples/aligned.py +++ b/samples/aligned.py @@ -26,11 +26,11 @@ rho = 1.191 corrections = ( - bemol.secondary.HubTipLoss.Prandtl, - bemol.secondary.SkewAngle.Burton, - bemol.secondary.YawModel.PittAndPeters, - bemol.secondary.DynamicInflow.Dummy, - bemol.secondary.TurbulentWakeState.Buhl, + bemol.secondary.hubTipLoss.Prandtl, + bemol.secondary.skewAngle.Burton, + bemol.secondary.yawModel.PittAndPeters, + bemol.secondary.dynamicInflow.Dummy, + bemol.secondary.turbulentWakeState.Buhl, ) # all angles are null, flow completly aligned diff --git a/samples/iea15mw.py b/samples/iea15mw.py index 2667b6e..d01f55e 100644 --- a/samples/iea15mw.py +++ b/samples/iea15mw.py @@ -28,8 +28,8 @@ rho = 1.225 corrections = ( - bemol.secondary.HubTipLoss.Prandtl, - bemol.secondary.TurbulentWakeState.Buhl, + bemol.secondary.hubTipLoss.Prandtl, + bemol.secondary.turbulentWakeState.Buhl, ) # all angles are null, flow completly aligned diff --git a/samples/pitch_maneuver.py b/samples/pitch_maneuver.py index 3a3acca..21c25b7 100644 --- a/samples/pitch_maneuver.py +++ b/samples/pitch_maneuver.py @@ -39,11 +39,11 @@ mexico = bemol.rotor.mexico corrections = [ - bemol.secondary.HubTipLoss.Prandtl, - bemol.secondary.SkewAngle.Burton, - bemol.secondary.YawModel.Dummy, - bemol.secondary.DynamicInflow.Knudsen, - bemol.secondary.TurbulentWakeState.Buhl, + bemol.secondary.hubTipLoss.Prandtl, + bemol.secondary.skewAngle.Burton, + bemol.secondary.yawModel.Dummy, + bemol.secondary.dynamicInflow.Knudsen, + bemol.secondary.turbulentWakeState.Buhl, ] times = np.arange(0.0,50.0,tStep) diff --git a/samples/yaw.py b/samples/yaw.py index f29d70c..cfbe58a 100644 --- a/samples/yaw.py +++ b/samples/yaw.py @@ -40,9 +40,9 @@ mexico = bemol.rotor.mexico corrections = ( - bemol.secondary.HubTipLoss.Prandtl, - bemol.secondary.SkewAngle.Burton, - bemol.secondary.TurbulentWakeState.Buhl, + bemol.secondary.hubTipLoss.Prandtl, + bemol.secondary.skewAngle.Burton, + bemol.secondary.turbulentWakeState.Buhl, ) diff --git a/samples/yaw_models.py b/samples/yaw_models.py index 556b32b..fbdb10b 100644 --- a/samples/yaw_models.py +++ b/samples/yaw_models.py @@ -39,9 +39,9 @@ mexico = bemol.rotor.mexico base_corrections = [ - bemol.secondary.HubTipLoss.Prandtl, - bemol.secondary.SkewAngle.Burton, - bemol.secondary.TurbulentWakeState.Buhl, + bemol.secondary.hubTipLoss.Prandtl, + bemol.secondary.skewAngle.Burton, + bemol.secondary.turbulentWakeState.Buhl, ] @@ -49,7 +49,7 @@ for yaw_model in ('Dummy','PittAndPeters','IFPEN'): corrections = base_corrections.copy() - corrections.append(getattr(bemol.secondary.YawModel,yaw_model)) + corrections.append(getattr(bemol.secondary.yawModel,yaw_model)) solver = bemol.ning.NingUncoupled(mexico,rho,corrections) forces, _, azimuths = solver.cycle( diff --git a/test.py b/test.py new file mode 100644 index 0000000..08077ab --- /dev/null +++ b/test.py @@ -0,0 +1,12 @@ + +import bemol + +"""Test definition of secondary effects.""" +dummy_rotor = bemol.rotor.mexico +# model = bemol.bem.BaseBEM(dummy_rotor,corrections={}) +model = bemol.bem.BaseBEM( + dummy_rotor, + corrections=[ + bemol.secondary.hubTipLoss.Prandtl, + bemol.secondary.skewAngle.Burton + ]) \ No newline at end of file diff --git a/tests/test_result.py b/tests/test_result.py index 377ccb2..53b298b 100644 --- a/tests/test_result.py +++ b/tests/test_result.py @@ -32,7 +32,8 @@ def change_test_dir(request,monkeypatch): @pytest.fixture(scope='module',autouse=True,) def reference_folder(request): # define the reference folder based on current folder - reference_path = request.config.reference + try: reference_path = request.config.reference + except AttributeError: return None # doing nothing if reference not given return Path( reference_path ).resolve() @@ -112,7 +113,7 @@ def test_compare(folder,run,request,reference_folder): new_data = pd.read_csv(new_filename) # only comparing if reference folder available - if reference_folder.exists(): + if reference_folder is not None and reference_folder.exists(): ref_filename = reference_folder / name_run / filename ref_data = pd.read_csv(ref_filename) pd.testing.assert_frame_equal(ref_data,new_data) diff --git a/tests/test_unit.py b/tests/test_unit.py index cc7820a..1824bab 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -18,6 +18,7 @@ def test_airfoil(): assert airfoil._cl.size > 1 + def test_rotor(): """Test definition of the rotor class.""" @@ -36,7 +37,7 @@ def test_rotor(): -def test_secondary_module(): +def test_secondary_module_default(): """Test definition of secondary effects.""" dummy_rotor = bemol.rotor.mexico @@ -49,49 +50,74 @@ def test_secondary_module(): assert hasattr(model.corrections,'yawModel') assert hasattr(model.corrections,'turbulentWakeState') - # check if the Dummy is selected everytime + + +def test_secondary_module_dummy(): + """Test if the Dummy is selected everytime""" + dummy_rotor = bemol.rotor.mexico + model = bemol.bem.BaseBEM(dummy_rotor) - assert model.corrections.hubTipLoss.__class__ is bemol.secondary.HubTipLoss.Dummy - assert model.corrections.skewAngle.__class__ is bemol.secondary.SkewAngle.Dummy - assert model.corrections.dynamicInflow.__class__ is bemol.secondary.DynamicInflow.Dummy - assert model.corrections.yawModel.__class__ is bemol.secondary.YawModel.Dummy - assert model.corrections.turbulentWakeState.__class__ is bemol.secondary.TurbulentWakeState.Dummy + assert model.corrections.hubTipLoss.__class__ is bemol.secondary.hubTipLoss.Dummy + assert model.corrections.skewAngle.__class__ is bemol.secondary.skewAngle.Dummy + assert model.corrections.dynamicInflow.__class__ is bemol.secondary.dynamicInflow.Dummy + assert model.corrections.yawModel.__class__ is bemol.secondary.yawModel.Dummy + assert model.corrections.turbulentWakeState.__class__ is bemol.secondary.turbulentWakeState.Dummy + + + +def test_secondary_module_custom_list(): + """Test define a model with a few custom corrections.""" + dummy_rotor = bemol.rotor.mexico - # define a model with a few custom corrections model = bemol.bem.BaseBEM( dummy_rotor, - corrections=[bemol.secondary.HubTipLoss.Prandtl,bemol.secondary.SkewAngle.Burton] + corrections=[bemol.secondary.hubTipLoss.Prandtl,bemol.secondary.skewAngle.Burton] ) - assert model.corrections.hubTipLoss.__class__ is bemol.secondary.HubTipLoss.Prandtl - assert model.corrections.skewAngle.__class__ is bemol.secondary.SkewAngle.Burton - assert model.corrections.dynamicInflow.__class__ is bemol.secondary.DynamicInflow.Dummy - assert model.corrections.yawModel.__class__ is bemol.secondary.YawModel.Dummy - assert model.corrections.turbulentWakeState.__class__ is bemol.secondary.TurbulentWakeState.Dummy + assert model.corrections.hubTipLoss.__class__ is bemol.secondary.hubTipLoss.Prandtl + assert model.corrections.skewAngle.__class__ is bemol.secondary.skewAngle.Burton + assert model.corrections.dynamicInflow.__class__ is bemol.secondary.dynamicInflow.Dummy + assert model.corrections.yawModel.__class__ is bemol.secondary.yawModel.Dummy + assert model.corrections.turbulentWakeState.__class__ is bemol.secondary.turbulentWakeState.Dummy + + + +def test_secondary_module_custom_dict(): + """Same as before, but with dict input define a model with a few custom corrections""" + dummy_rotor = bemol.rotor.mexico - # same as before, but with dict input define a model with a few custom corrections model = bemol.bem.BaseBEM( dummy_rotor, corrections=dict( - hubTipLoss=bemol.secondary.HubTipLoss.Prandtl, - skewAngle=bemol.secondary.SkewAngle.Burton + hubTipLoss=bemol.secondary.hubTipLoss.Prandtl, + skewAngle=bemol.secondary.skewAngle.Burton ) ) - assert model.corrections.hubTipLoss.__class__ is bemol.secondary.HubTipLoss.Prandtl - assert model.corrections.skewAngle.__class__ is bemol.secondary.SkewAngle.Burton - assert model.corrections.dynamicInflow.__class__ is bemol.secondary.DynamicInflow.Dummy - assert model.corrections.yawModel.__class__ is bemol.secondary.YawModel.Dummy - assert model.corrections.turbulentWakeState.__class__ is bemol.secondary.TurbulentWakeState.Dummy - - # mix class and instance - hub_corr = bemol.secondary.HubTipLoss.Prandtl() + assert model.corrections.hubTipLoss.__class__ is bemol.secondary.hubTipLoss.Prandtl + assert model.corrections.skewAngle.__class__ is bemol.secondary.skewAngle.Burton + assert model.corrections.dynamicInflow.__class__ is bemol.secondary.dynamicInflow.Dummy + assert model.corrections.yawModel.__class__ is bemol.secondary.yawModel.Dummy + assert model.corrections.turbulentWakeState.__class__ is bemol.secondary.turbulentWakeState.Dummy + + + +def test_secondary_module_custom_mixed(): + """Test mix class and instance for correction input.""" + dummy_rotor = bemol.rotor.mexico + + hub_corr = bemol.secondary.hubTipLoss.Prandtl() model = bemol.bem.BaseBEM( - dummy_rotor,corrections=[hub_corr,bemol.secondary.SkewAngle.Burton] + dummy_rotor,corrections=[hub_corr,bemol.secondary.skewAngle.Burton] ) assert model.corrections.hubTipLoss is hub_corr - assert model.corrections.skewAngle.__class__ is bemol.secondary.SkewAngle.Burton + assert model.corrections.skewAngle.__class__ is bemol.secondary.skewAngle.Burton + - # check if corrections of nested solvers are the same instance +def test_secondary_module_nested_solvers(): + """Check if corrections of nested solvers are the same instance.""" ## TOOD: maybe this is not always the wanted behavior! + dummy_rotor = bemol.rotor.mexico + corrections = {} + solver_uncoupled = bemol.ning.NingUncoupled(dummy_rotor,1.0,corrections) solver_coupled = bemol.ning.NingCoupled(dummy_rotor,1.0,corrections) assert solver_coupled.corrections.dynamicInflow is not solver_uncoupled.corrections.dynamicInflow From 43d05ea3cbfc57764566f8f04a435f6c285205d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wagner=20Gon=C3=A7alves=20Pinto?= <255828055+wagnerjpinto@users.noreply.github.com> Date: Tue, 28 Apr 2026 10:03:25 +0200 Subject: [PATCH 04/17] update version of matplotlib --- environment.yaml | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/environment.yaml b/environment.yaml index d457504..a36cd9e 100644 --- a/environment.yaml +++ b/environment.yaml @@ -7,5 +7,5 @@ dependencies: - scipy==1.14.1 - pandas==3.0.2 - pyyaml==6.0.2 - - matplotlib==3.10.1 + - matplotlib==3.10.9 - pytest==8.3.4 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 131ec14..860d242 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ exceptiongroup==1.3.1 fonttools==4.62.1 iniconfig==2.3.0 kiwisolver==1.5.0 -matplotlib==3.10.1 +matplotlib==3.10.9 munkres==1.1.4 numpy==1.26.4 packaging==26.2 From 074e645d9833d234082de6c451b1694cf16607e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wagner=20Gon=C3=A7alves=20Pinto?= <255828055+wagnerjpinto@users.noreply.github.com> Date: Tue, 28 Apr 2026 10:08:18 +0200 Subject: [PATCH 05/17] add README for secondary --- bemol/secondary/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 bemol/secondary/README.md diff --git a/bemol/secondary/README.md b/bemol/secondary/README.md new file mode 100644 index 0000000..14c06a9 --- /dev/null +++ b/bemol/secondary/README.md @@ -0,0 +1,15 @@ + +# Secondary correction models + +`hubTipLoss.py`: hub/tip loss correction. + +`dynamicInflow.py`: dynamic inflow correction. + +`skewAngle.py`: skew angle correction. + +`turbulentWakeState.py`: turbulent wake state correction. + +`yawModel.py`: yaw models. + +In order to implement a new model, go to the corresponding file and add +a new class similar to the ones already present. \ No newline at end of file From 4a1b4ed9bd1f43cbf6254b51d66c0c538cb35593 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wagner=20Gon=C3=A7alves=20Pinto?= <255828055+wagnerjpinto@users.noreply.github.com> Date: Tue, 28 Apr 2026 10:20:56 +0200 Subject: [PATCH 06/17] add missing refs to models --- bemol/secondary/dynamicInflow.py | 8 +++++++- bemol/secondary/skewAngle.py | 6 +++++- bemol/secondary/turbulentWakeState.py | 4 +++- bemol/secondary/yawModel.py | 7 ++++--- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/bemol/secondary/dynamicInflow.py b/bemol/secondary/dynamicInflow.py index 8d4a72f..9917452 100644 --- a/bemol/secondary/dynamicInflow.py +++ b/bemol/secondary/dynamicInflow.py @@ -12,7 +12,13 @@ def __call__(self,axialInduction,*args,**kwargs): class Knudsen: - """Knudsen dynamic inflow model.""" + """Knudsen dynamic inflow model. + + T. Knudsen and T. Bak, "Simple model for describing and estimating wind + turbine dynamic inflow," 2013 American Control Conference, + Washington, DC, USA, 2013, pp. 640-646, doi: 10.1109/ACC.2013.6579909. + + """ def __init__(self,alphaDynamic=0.3,tauScale=3.0) -> None: self._alpha0 = alphaDynamic diff --git a/bemol/secondary/skewAngle.py b/bemol/secondary/skewAngle.py index 93f6ff4..993ffd1 100644 --- a/bemol/secondary/skewAngle.py +++ b/bemol/secondary/skewAngle.py @@ -11,7 +11,11 @@ def __call__(self,axialInduction,*args,**kwargs): class Burton: - """Burton skewed wake model.""" + """Burton skewed wake model. + + Burton "Handbook of wind energy", 2001, page 105. + + """ def __init__(self,) -> None: return diff --git a/bemol/secondary/turbulentWakeState.py b/bemol/secondary/turbulentWakeState.py index 9a5edcc..7d59c40 100644 --- a/bemol/secondary/turbulentWakeState.py +++ b/bemol/secondary/turbulentWakeState.py @@ -22,7 +22,9 @@ def __call__(self,axialInduction,lossFactor,skewAngle,*args,**kwargs): class Buhl: """Buhl's empirical high-induction correction model. - https://docs.nlr.gov/docs/fy05osti/36834.pdf + Buhl, Jr, M L. "New Empirical Relationship between Thrust Coefficient and + Induction Factor for the Turbulent Windmill State.", Aug. 2005. + https://doi.org/10.2172/15016819 """ diff --git a/bemol/secondary/yawModel.py b/bemol/secondary/yawModel.py index a585202..faf80b8 100644 --- a/bemol/secondary/yawModel.py +++ b/bemol/secondary/yawModel.py @@ -12,8 +12,8 @@ def __init__(self) -> None: def __call__(self,axialInduction,*args,**kwargs): return axialInduction - - + + class PittAndPeters: """Pitt & Peters skewed wake model. @@ -34,7 +34,8 @@ def __call__(self,axialInduction,wakeSkewAngle,azimuthAngle,radius,hubRadius,tip return axialInduction*( 1. + self.factor*np.tan(wakeSkewAngle/2.)*radius/tipRadius*np.sin(azimuthAngle) ) - + + class IFPEN: """IFPEN skewed wake model. From 363041a88e62b7855248c53fdda76601a181bb99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wagner=20Gon=C3=A7alves=20Pinto?= <255828055+wagnerjpinto@users.noreply.github.com> Date: Tue, 28 Apr 2026 10:23:49 +0200 Subject: [PATCH 07/17] change workflow to use pip --- .../{python-test-conda.yml => python-test-pip.yml} | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) rename .github/workflows/{python-test-conda.yml => python-test-pip.yml} (78%) diff --git a/.github/workflows/python-test-conda.yml b/.github/workflows/python-test-pip.yml similarity index 78% rename from .github/workflows/python-test-conda.yml rename to .github/workflows/python-test-pip.yml index 3692735..a99e653 100644 --- a/.github/workflows/python-test-conda.yml +++ b/.github/workflows/python-test-pip.yml @@ -1,4 +1,4 @@ -name: Python Package using Conda +name: Python Package using pip on: [push] @@ -15,16 +15,13 @@ jobs: uses: actions/setup-python@v3 with: python-version: '3.11.2' - - - name: Add conda to system path - run: | - # $CONDA is an environment variable pointing to the root of the miniconda directory - echo $CONDA/bin >> $GITHUB_PATH + cache: pip - name: Install dependencies run: | - conda env update --file environment.yaml - conda install flake8 + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install flake8 - name: Lint with flake8 run: | From f07625001ddf670847e2e6b7911239cc70665e60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wagner=20Gon=C3=A7alves=20Pinto?= <255828055+wagnerjpinto@users.noreply.github.com> Date: Tue, 28 Apr 2026 10:25:08 +0200 Subject: [PATCH 08/17] remove cache of pip --- .github/workflows/python-test-pip.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/python-test-pip.yml b/.github/workflows/python-test-pip.yml index a99e653..a96b0d5 100644 --- a/.github/workflows/python-test-pip.yml +++ b/.github/workflows/python-test-pip.yml @@ -15,7 +15,6 @@ jobs: uses: actions/setup-python@v3 with: python-version: '3.11.2' - cache: pip - name: Install dependencies run: | From 237df8c3ea165b2f4e03e9087769836abd15db1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wagner=20Gon=C3=A7alves=20Pinto?= <255828055+wagnerjpinto@users.noreply.github.com> Date: Tue, 28 Apr 2026 10:26:54 +0200 Subject: [PATCH 09/17] fix activate in test --- .github/workflows/python-test-pip.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-test-pip.yml b/.github/workflows/python-test-pip.yml index a96b0d5..e3a82e2 100644 --- a/.github/workflows/python-test-pip.yml +++ b/.github/workflows/python-test-pip.yml @@ -38,6 +38,6 @@ jobs: - name: Test with pytest run: | - source activate bemol + source .bemol/bin/activate pytest tests/ -vvs --reference tests/ref/ \ No newline at end of file From 9cf1713ed899bd59506a95619f2e96e7da6baba6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wagner=20Gon=C3=A7alves=20Pinto?= <255828055+wagnerjpinto@users.noreply.github.com> Date: Tue, 28 Apr 2026 10:30:05 +0200 Subject: [PATCH 10/17] fix install of venv .bemol --- .github/workflows/python-test-pip.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/python-test-pip.yml b/.github/workflows/python-test-pip.yml index e3a82e2..18cbe08 100644 --- a/.github/workflows/python-test-pip.yml +++ b/.github/workflows/python-test-pip.yml @@ -19,6 +19,8 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip + python3 -m venv .bemol + source .bemol/bin/activate pip install -r requirements.txt pip install flake8 From 1d9cf01f945b7957a98448d55ca55f59e5195b98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wagner=20Gon=C3=A7alves=20Pinto?= <255828055+wagnerjpinto@users.noreply.github.com> Date: Tue, 28 Apr 2026 10:31:38 +0200 Subject: [PATCH 11/17] fix load of venv .bemol --- .github/workflows/python-test-pip.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/python-test-pip.yml b/.github/workflows/python-test-pip.yml index 18cbe08..f16491f 100644 --- a/.github/workflows/python-test-pip.yml +++ b/.github/workflows/python-test-pip.yml @@ -26,6 +26,7 @@ jobs: - name: Lint with flake8 run: | + source .bemol/bin/activate # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide From 153e26781665410bc1aa89ce23f835874b68f0a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wagner=20Gon=C3=A7alves=20Pinto?= <255828055+wagnerjpinto@users.noreply.github.com> Date: Tue, 28 Apr 2026 10:35:07 +0200 Subject: [PATCH 12/17] remove the use of venv --- .github/workflows/python-test-pip.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/python-test-pip.yml b/.github/workflows/python-test-pip.yml index f16491f..bf1c08f 100644 --- a/.github/workflows/python-test-pip.yml +++ b/.github/workflows/python-test-pip.yml @@ -19,14 +19,11 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python3 -m venv .bemol - source .bemol/bin/activate pip install -r requirements.txt pip install flake8 - name: Lint with flake8 run: | - source .bemol/bin/activate # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide @@ -41,6 +38,5 @@ jobs: - name: Test with pytest run: | - source .bemol/bin/activate pytest tests/ -vvs --reference tests/ref/ \ No newline at end of file From eae049b46ba4606bc8fa5667b747885db2133e0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wagner=20Gon=C3=A7alves=20Pinto?= <255828055+wagnerjpinto@users.noreply.github.com> Date: Tue, 28 Apr 2026 10:55:41 +0200 Subject: [PATCH 13/17] update actions so release calls test directly --- .github/workflows/python-release.yml | 45 +++++++++------------------ .github/workflows/python-test-pip.yml | 28 ++++++++++++++--- 2 files changed, 37 insertions(+), 36 deletions(-) diff --git a/.github/workflows/python-release.yml b/.github/workflows/python-release.yml index fc61205..00ebeb6 100644 --- a/.github/workflows/python-release.yml +++ b/.github/workflows/python-release.yml @@ -1,12 +1,17 @@ name: Create and upload release on: - push: - tags: - - 'v*' + workflow_run: + workflows: ["Python Package using pip"] + types: + - completed jobs: - test-linux: + release: + # Only run if the triggering run was for a tag starting with v + if: > + github.event.workflow_run.conclusion == 'success' && + startsWith(github.event.workflow_run.head_branch, 'v') runs-on: ubuntu-latest strategy: max-parallel: 5 @@ -14,34 +19,12 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Python 3.11.2 - uses: actions/setup-python@v3 + - name: Download pytest-results artifact from previous workflow + uses: actions/download-artifact@v4 with: - python-version: '3.11.2' - - - name: Add conda to system path - run: | - # $CONDA is an environment variable pointing to the root of the miniconda directory - echo $CONDA/bin >> $GITHUB_PATH - - - name: Install dependencies - run: | - conda env update --file environment.yaml - - - name: Lint with flake8 - run: | - conda install flake8 - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - - name: Test with pytest - run: | - source activate bemol - pytest tests/ -vvs - # store the latest run results as release artifact - zip -r pytest-results.zip tests/results/ + name: pytest-results + run-id: ${{ github.event.workflow_run.id }} + path: . - name: Create Release id: create_release diff --git a/.github/workflows/python-test-pip.yml b/.github/workflows/python-test-pip.yml index bf1c08f..1e8d113 100644 --- a/.github/workflows/python-test-pip.yml +++ b/.github/workflows/python-test-pip.yml @@ -1,6 +1,12 @@ name: Python Package using pip -on: [push] +on: + push: + branches: + - '*' + tags: + - '*' + jobs: test-linux: @@ -29,14 +35,26 @@ jobs: # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - # TODO: conside the latest release, not a hardcoded one! + # considers the latest release, not a hardcoded one! - name: Get reference results run: | - curl -L -o pytest-results.zip "https://github.com/ifpen/bemol/releases/download/v0.0.1/pytest-results.zip" - unzip pytest-results.zip + URL=$(curl -s https://api.github.com/repos/ifpen/bemol/releases/latest \ + | jq -r '.assets[] | select(.name=="pytest-results.zip") | .browser_download_url') + if [ -z "$URL" ] || [ "$URL" = "null" ]; then + echo "pytest-results.zip not found in latest release assets" + exit 1 + fi + curl -L -o pytest-results.zip "$URL" mv tests/results tests/ref/ - name: Test with pytest run: | pytest tests/ -vvs --reference tests/ref/ - \ No newline at end of file + zip -r pytest-results.zip tests/results + + - name: Upload pytest-results artifact + uses: actions/upload-artifact@v4 + with: + name: pytest-results + path: pytest-results.zip + retention-minutes: 30 \ No newline at end of file From d0f8b9483b14ea92a3945d1572de2a5c70f5fd16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wagner=20Gon=C3=A7alves=20Pinto?= <255828055+wagnerjpinto@users.noreply.github.com> Date: Tue, 28 Apr 2026 11:01:43 +0200 Subject: [PATCH 14/17] fix unzip of rerfence --- .github/workflows/python-test-pip.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-test-pip.yml b/.github/workflows/python-test-pip.yml index 1e8d113..c659640 100644 --- a/.github/workflows/python-test-pip.yml +++ b/.github/workflows/python-test-pip.yml @@ -45,12 +45,12 @@ jobs: exit 1 fi curl -L -o pytest-results.zip "$URL" + zip -r pytest-results.zip tests/results mv tests/results tests/ref/ - name: Test with pytest run: | pytest tests/ -vvs --reference tests/ref/ - zip -r pytest-results.zip tests/results - name: Upload pytest-results artifact uses: actions/upload-artifact@v4 From 966e189d1ce3cd03dff43e189b76ab1594bedcfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wagner=20Gon=C3=A7alves=20Pinto?= <255828055+wagnerjpinto@users.noreply.github.com> Date: Tue, 28 Apr 2026 11:04:26 +0200 Subject: [PATCH 15/17] fix unzip --- .github/workflows/python-test-pip.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/python-test-pip.yml b/.github/workflows/python-test-pip.yml index c659640..3d0add3 100644 --- a/.github/workflows/python-test-pip.yml +++ b/.github/workflows/python-test-pip.yml @@ -45,12 +45,13 @@ jobs: exit 1 fi curl -L -o pytest-results.zip "$URL" - zip -r pytest-results.zip tests/results + unzip pytest-results.zip mv tests/results tests/ref/ - name: Test with pytest run: | pytest tests/ -vvs --reference tests/ref/ + zip -r pytest-results.zip tests/results - name: Upload pytest-results artifact uses: actions/upload-artifact@v4 From 9348e984b22a3bc87b352c51d8bb19a623758a8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wagner=20Gon=C3=A7alves=20Pinto?= <255828055+wagnerjpinto@users.noreply.github.com> Date: Tue, 28 Apr 2026 11:06:45 +0200 Subject: [PATCH 16/17] fix retention time of artifact --- .github/workflows/python-test-pip.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-test-pip.yml b/.github/workflows/python-test-pip.yml index 3d0add3..0f851e7 100644 --- a/.github/workflows/python-test-pip.yml +++ b/.github/workflows/python-test-pip.yml @@ -58,4 +58,4 @@ jobs: with: name: pytest-results path: pytest-results.zip - retention-minutes: 30 \ No newline at end of file + retention-days: 0.020833 # 30 minutes \ No newline at end of file From c41e1819b7899e3bc485f5e66b682c0b9f0245e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wagner=20Gon=C3=A7alves=20Pinto?= <255828055+wagnerjpinto@users.noreply.github.com> Date: Tue, 28 Apr 2026 11:12:01 +0200 Subject: [PATCH 17/17] make test of result not exact --- tests/test_result.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_result.py b/tests/test_result.py index 53b298b..4536630 100644 --- a/tests/test_result.py +++ b/tests/test_result.py @@ -116,7 +116,10 @@ def test_compare(folder,run,request,reference_folder): if reference_folder is not None and reference_folder.exists(): ref_filename = reference_folder / name_run / filename ref_data = pd.read_csv(ref_filename) - pd.testing.assert_frame_equal(ref_data,new_data) + pd.testing.assert_frame_equal( + ref_data,new_data, + check_exact=False,rtol=0.0001, + ) else: # for the moment just doing nothing if reference folder not # available! Make it at least a warning in the future.