diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 6b9cf15..c245c14 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -1,29 +1,29 @@ -name: Documentation +name: Documentation on: push: branches: - master - main permissions: - contents: write + contents: read + pages: write + id-token: write jobs: deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Configure Git Credentials - run: | - git config user.name github-actions[bot] - git config user.email 41898282+github-actions[bot]@users.noreply.github.com + - uses: actions/configure-pages@v5 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 with: - python-version: "3.x" - - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV - - uses: actions/cache@v4 + python-version: "3.12" + - run: pip install -e '.[docs]' + - run: zensical build --clean + - uses: actions/upload-pages-artifact@v4 with: - key: mkdocs-material-${{ env.cache_id }} - path: ~/.cache - restore-keys: | - mkdocs-material- - - run: pip install mkdocs-material markdown-include pymdown-extensions mkdocstrings mkdocstrings-python - - run: mkdocs gh-deploy --force --clean \ No newline at end of file + path: site + - uses: actions/deploy-pages@v4 + id: deployment diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 1a0eab1..ff94bb5 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -1,4 +1,4 @@ -name: Tests for MORS +name: MORS CI Test Suite on: push: @@ -66,6 +66,6 @@ jobs: filename: covbadge.svg label: Coverage message: ${{ env.total }}% - minColorRange: 10 - maxColorRange: 90 + minColorRange: 40 + maxColorRange: 100 valColorRange: ${{ env.total }} diff --git a/.gitignore b/.gitignore index 69d4598..7b0d4de 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,6 @@ fs255_grid* # Debug #*.png nogit* + +#zensical site +site/ \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a8500d8..f9444f5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,13 +2,13 @@ ### Building the documentation -The documentation is written in [markdown](https://www.markdownguide.org/basic-syntax/), and uses [mkdocs](https://www.mkdocs.org/) to generate the pages. +The documentation is written in [markdown](https://www.markdownguide.org/basic-syntax/), and uses [Zensical](https://www.zensical.org/) to generate the pages. To build the documentation for yourself: ```console pip install -e .[docs] -mkdocs serve +zensical serve ``` You can find the documentation source in the [docs](https://github.com/FormingWorlds/MORS/tree/main/docs) directory. diff --git a/README.md b/README.md index b8ee276..d7c8150 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,35 @@ # MODEL FOR ROTATION OF STARS (MORS) -MORS is a Python package (distributed as `fwl-mors`) used in the [PROTEUS framework](https://github.com/FormingWorlds/PROTEUS) to model **stellar rotation** and **high-energy emission (X-ray, EUV, Ly-α)** evolution. -It implements the model of **Johnstone et al. (2021)** and provides stellar evolution quantities based on **Spada et al. (2013)** (plus optional Baraffe tracks). +[![MORS CI Test Suite](https://github.com/FormingWorlds/MORS/actions/workflows/tests.yaml/badge.svg)](https://github.com/FormingWorlds/MORS/actions) +![Coverage](https://gist.githubusercontent.com/lsoucasse/a25c37a328839edd00bb32d8527aec30/raw/covbadge.svg) +[![License](https://img.shields.io/github/license/FormingWorlds/MORS?label=License)](https://github.com/FormingWorlds/MORS/blob/main/LICENSE.md) +[![PyPI](https://img.shields.io/pypi/v/fwl-mors?label=PyPI)](https://pypi.org/project/fwl-mors/) -> **Note:** This version includes the fix for the EUV1 → EUV2 conversion. +MORS is a Python package for modelling the rotational spin-down and high-energy (X-ray, EUV, Ly-α) emission evolution of low-mass stars. It implements the model of [Johnstone, Bartel & Güdel (2021)](https://www.aanda.org/articles/aa/abs/2021/05/aa38407-20/aa38407-20.html) and is the stellar evolution model integrated into the [PROTEUS](https://proteus-framework.org/PROTEUS) framework. + +**Documentation:** https://proteus-framework.org/MORS/ ## Install ```bash pip install fwl-mors -``` - -### Required data: stellar evolution tracks -Download the stellar evolution tracks (stored on OSF): - -```bash mors download all -mors env -``` - -By default, data follow the XDG base directory convention. You can override the data root with: - -```bash -export FWL_DATA=/path/to/data -``` - -Or set a per-script directory: - -```python -import mors -star_evo = mors.StarEvo(starEvoDir="path/to/evolution-tracks") ``` ## Quickstart ```python import mors -import matplotlib.pyplot as plt -star = mors.Star(Mstar=1.0, Prot=2.7) # 1 Msun star, initial rotation period in days (at age ~1 Myr) -print(star.Lx(150.0)) # X-ray luminosity at 150 Myr - -plt.plot(star.AgeTrack, star.LxTrack) -plt.xlabel(f"Age [{star.Units['Age']}]") -plt.ylabel(f"Lx [{star.Units['Lx']}]") -plt.show() +star = mors.Star(Mstar=1.0, Prot=2.7) # 1 Msun, initial rotation period at 1 Myr +print(star.Lx(150.0)) # X-ray luminosity at 150 Myr [erg s-1] ``` -## Documentation - -You can find the complete documentation [here](https://proteus-framework.org/MORS/). - ## Citation -When publishing results computed with MORS, please cite: -- **Johnstone et al. (2021)** for the rotation/XUV evolution model -- **Spada et al. (2013)** for the stellar evolution tracks used for stellar properties +If you use MORS in published work, please cite: + +- [Johnstone, Bartel & Güdel (2021)](https://www.aanda.org/articles/aa/abs/2021/05/aa38407-20/aa38407-20.html), *A&A*, 649, A96 (rotation and XUV evolution model) +- [Spada et al. (2013)](https://iopscience.iop.org/article/10.1088/0004-637X/776/2/87/meta), *ApJ*, 776, 87 (stellar evolution tracks) -If you use the model cluster distribution/percentiles, also cite the rotation-measurement sources referenced in **Johnstone et al. (2020)** (Table 1, ~150 Myr bin). +If you use the model cluster distribution or percentiles, also cite the rotation-measurement sources in Table 1 of Johnstone et al. (2021). \ No newline at end of file diff --git a/README_old.md b/README_old.md deleted file mode 100644 index 5ccf69d..0000000 --- a/README_old.md +++ /dev/null @@ -1,526 +0,0 @@ -![Coverage](https://gist.githubusercontent.com/lsoucasse/a25c37a328839edd00bb32d8527aec30/raw/covbadge.svg) - -# MODEL FOR ROTATION OF STARS (MORS) - -**This code is distributed as a python package for the purpose of the [PROTEUS framework](https://github.com/FormingWorlds/PROTEUS), a coupled simulation tool for the long-term evolution of atmospheres and interiors of rocky planets. -The MORS package solves specifically the stellar rotation and evolution. It is based on the [original code](https://www.aanda.org/articles/aa/pdf/2021/05/aa38407-20.pdf) and model developed by Colin P. Johnstone.** - -**Original Author:** Colin P. Johnstone - -This code solves the stellar rotation and XUV evolution model presented in Johnstone et al. (2021). The package can be used to calculate evolutionary tracks for stellar rotaton and X-ray, EUV, and Ly-alpha emission for stars with masses between 0.1 and 1.25 Msun and has additional functionality such as allowing the user to get basic stellar parameters such as stellar radius and luminosity as functions of mass and age using the stellar evolution models of Spada et al. (2013). When publishing results that were calculated using this code, both the Johnstone et al. (2020) paper and Spada et al. (2013) should be cited. - -**NOTE:** This version contains the fix for the error in the equation converting EUV1 to EUV2. - -## 1. INSTALLATION - -### 1.1. Basic install - -The Forming Worlds Mors package is available on PyPI. Run the following command to install - -``` -pip install fwl-mors -``` -### 1.2. Developer install - -You can alternatively download the source code from GitHub somewhere on your computer using - -``` -git clone git@github.com:FormingWorlds/MORS.git -``` - -Then run the following command inside the main directory to install the code (check the pyproject.toml file for dependencies) - -``` -pip install -e . -``` - - -### 1.3. Stellar evolution tracks - -The code requires also a set of stellar evolution data, stored in the [OSF repository](https://osf.io/9u3fb/). - -You can use `mors download all` to download the data. This will download and extract package stellar evolution tracks data. - -By default, MORS stores the data in based on the [XDG specification](https://specifications.freedesktop.org/basedir-spec/latest/). -You can check the location by typing `mors env` in your terminal. -You can override the path using the `FWL_DATA` environment variable, e.g. - -```console -export FWL_DATA=... -``` - -Where ... should be replaced with the path to your main data directory. To make this permanent on Ubuntu, use - -```console -gedit ~/.profile -``` - -and add the export command to the bottom of the file. - -Alternatively, when creating a star object in your Python script, you can specify the path to a directory where evolution tracks are stored using the starEvoDir keyword - -```python -import mors -myStar = mors.StarEvo(starEvoDir=...) -``` - -where ... can be given as the path relative to the current directory. When this is done, no environmental variable needs to be set. - -## 2. EVOLUTIONARY CALCULATIONS - -The main way that the user is meant to interact with the code is through the Star class. The user can create an instance of the star class, specifying only the mass and star's initial (1 Myr) rotation rate using the Mstar and Omega0 keyword arguments. This can be done for a star with a mass of 1 Msun and an initial rotation of 10x the modern Sun using - -```python -star = mors.Star(Mstar=1.0, Omega=10.0) -``` - -The user can instead set the initial rotation rate using the Prot keyword argument giving the surface rotation period in days. - -```python -star = mors.Star(Mstar=1.0, Prot=2.7) -``` - -Alternatively, the user can specify starting values for both the core and envelope rotation rates using the OmegaEnv and OmegaCore arguments, though this is usually not recommended. - -If the user instead specifies an age the Age and Omega keyword argument, the code will look for the track that passes through the specified surface rotation rate at the specified age. The surface rotation rate can be specified either using Omega or OmegaEnv. - -```python -star = mors.Star(Mstar=1.0, Age=100.0, Omega=50.0) -``` - -In this case, the code will search for a rotation track that has a surface rotation rate that is 50x the current Sun's at an age of 100 Myr. It is not recommended to do this when the age of the star is so large that the rotation rates of stars with different initial rotation rates have converged (i.e. Age should only be specified if it is low enough that there is still a large spread in rotation rates for that mass at that age). In not all cases will it actually be able to find a physically realistic rotation rate, for example if the rotation rate specified is unreaslistically large. - -The code will calculate evolutionary tracks for all rotation and activity quantities available. These include surface rotation rate, OmegaEnv, and X-ray luminosity, Lx. To see a list of quantities with calculated evolutionary tracks, use the PrintAvailableTracks() attribute of the Star class. - -```python -star.PrintAvailableTracks() -``` - -This gives units for all quantities. This can also be used to get units for each of the available quantities. Tracks for each of the quantities are held in numpy arrays and can be accessed using the Tracks dictionary, which is an attribute of the Star class. For example, the user can plot evolutionary tracks for Lx using - -```python -plt.plot(star.Tracks['Age'], star.Tracks['Lx']) -``` - -Alternatively, numpy arrays for each quantity are attributes of the Star class with the name of the quantity followed by 'Track', so the above can be replaced with - -```python -plt.plot(star.AgeTrack, star.LxTrack) -``` - -Units are held in the dictionary Units, which is also an attribute of the Star class. For example, to see the units of Lx, the user can use - -```python -print(star.Units['Lx']) -``` - -The value of one of these quantities at a given age can be output using the Value() attribute of the Star class giving age the Myr and a string with the name of the desired quantity. These can be given either as positional or as keyword arguments. For example, to print Lx at 150 Myr to the screen you can use - -```python -print(star.Value(150.0,'Lx')) -``` - -or - -```python -print(star.Value(Age=150.0, Quantity='Lx')) -``` - -More simply, the user can use the function with the name of the desired quantity, so the two above lines could be replaced with - -```python -print(star.Lx(150.0)) -``` - -Instances of the Star class can be saved using the Save (or save) functions, - -```python -star.Save() -``` - -By default, the data will be saved into a file called 'star.pickle' but using the user can specify the name of the file using the filename argument in the call to Save(). Saved stars can be loaded using the Load() function. - -```python -star = mors.Load("star.pickle") -``` - -## 3. MODEL DISTRIBUTION AND PERCENTILES - -It is possible when creating an instance of the Star class to specify the initial rotation as a percentile of the model distribution from Johnstone et al. (2020). This model distribution is composed of measured rotation distributions from several clusters with ages of ~150 Myr evolved back to 1 Myr. To get the masses and initial rotation rates of the model cluster, use the function ModelCluster(). - -```python -Mstar , Omega = ModelCluster() -``` - -If this model cluster is used in any research, please cite the papers listed in Table 1 of Johnstone et al. (2020) for the 150 Myr age bin as the original sources for the rotation measurements (note that the function ModelCluster does not return these measurements, but 1 Myr rotation rates for these stars derived from their measurments using the rotational evolution model of Johnstone et al. 2020). To evolve this cluster, use the Cluster class as described below. - -When creating an instance of the Star class, use the keyword argument percentile to set the initial rotation rate to a percentile of this distribution. This can either be given as a float or int between 0 and 100 or as a string, with the three options being 'slow', 'medium', and 'fast', corresponding to the 5th, 50th, and 95th percentiles of the rotation distribution. For example - -```python -star = mors.Star(Mstar=1.0, percentile=5.0) -``` - -This creates a solar mass star with an initial rotation rate equal to the 5th percentile of the rotation distribution at 1 Myr, as determined from our model cluster. This is equivalent to - -```python -star = mors.Star(Mstar=1.0, percentile='slow') -``` - -To calculate this percentile, the parameter dMstarPer is used and can be set if the user sets up their own parameters as discussed above. This is set by default to 0.1, meaning that all stars within 0.1 Msun of the specified Mstar will be considered. - -Regardless of how a star is setup, the percentile in the model distribution is calculated after the evolutionary tracks are calculated and stored in the percentile attribute of the class. It can be seen using - -```python -print(star.percentile) -``` - -If the user wants to find out the percentile of a given rotation race for a given mass, they can use the the Percentile function, specifying the star's mass and surface rotation rate, either as a rotation velocity in OmegaSun using the Omega (or OmegaEnv) keyword argument, or as a rotation period in days using the Prot keyword argument. For example - -```python -print(mors.Percentile(Mstar=1.0, Omega=10.0)) -``` - -This will print to the screen where in the starting (1 Myr) distribution a solar mass star rotating at 10x the current Sun is, using the model distribution from Johnstone et al. (2020). Or for a star was a 1 day rotation period - -```python -print(mors.Percentile(Mstar=1.0, Prot=1.0)) -``` - -Alternatively, the user can specify the percentile and get the corresponding rotation rate - -```python -print(mors.Percentile(Mstar=1.0, percentile=10.0)) -``` - -This prints the rotation rate of the 10th percentile for solar mass stars in OmegaSun. If the user wants to use a different cluster distribution, instead of the 1 Myr model distribution used in Johnstone et al. (2020), the masses and rotation rates of the stars in this distribution can be input. The masses are input as an array of masses in Msun using the MstarDist keyword argument, and the rotation rates can be input either as an array of surface angular velocities in OmegaSun using the OmegaDist keyword argument or as an array of rotation periods in days using the ProtDist keyword argument. - -## 4. SETTING SIMULATION PARAMETERS - -In addition to just the masses and initial rotation rates, the basic behavior of the code depends on a large number of parameters all of which have default values and do not need to be changed by the user. These default values cause the code to run the evolutionary model for rotation and XUV emission described in Johnstone et al. (2020) and generally do not need to be changed by the user. However, if the user wishes, these parameters can be changed. - -In general, simulation parameters are held in a dictionary called 'params' within the code, and the user can specify this parameter dictionary when creating a instance of the Star class. When this is not done, the code will use the paramsDefault dictionary created in parameters.py in the source code. To use user specified set of parameters, the user must first generate a parameter dictionary using the NewParams() function. - -```python -myParams = mors.NewParams() -``` - -This will create a dictionary that is identical to paramsDefault that the user can edit as they want. For example, to change the parameter param1 to 1.5 and param2 to 2.5, use - -```python -myParams = mors.NewParams() -myParams['param1'] = 1.5 -myParams['param2'] = 2.5 -``` - -Alternatively, and probably preferrably, this can also be done in the initial call to NewParams using keyword arguments. - -```python -myParams = mors.NewParams(param1=1.5, param2=2.5) -``` - -This user should then input this as a keyword argument when creating an instance of the Star class. - -```python -star = mors.Star(Mstar=1.0, Omega=10.0, params=myParams) -``` - -To see a complete list of all parameters that should be set, the user can use the PrintParams() function. - -```python -mors.PrintParams() -``` - -Or can simply look into the parameters.py file in the main directory where Mors is installed. - -The user can set the keyword parameter AgesOut when creating a new Star object to a number or a numpy array and this will cause the code to only include these ages and the starting age in the evolutionary tracks. The ages should be given in Myr. For example - -```python -star = mors.Star(Mstar=1.0, Omega=10.0, AgesOut=100.0) -``` - -will cause the evolutionary tracks to only contain the starting age of 1 Myr and the specified age of 100 Myr. Similarly - -```python -star = mors.Star(Mstar=1.0, Omega=10.0, AgesOut=np.array([100.0,200.0,300.0,400.0])) -``` - -will cause the evolutionary tracks to contain 1 Myr, and the specified 100, 200, 300, and 400 Myr ages. The simulations will also end at the oldest year in the array. This array should be in ascending order. This is not recommended if the user wants to extract quantities at arbitrary ages along evolutionary tracks, for example using star.Lx(Age), since these functions interpolate between the values and if there are not enough age bins in the evolutionary tracks then these results can be inaccurate. Note that having two many age bins in the AgesOut array, e.g. with AgesOut=np.linspace(1.0,5000.0,10000), will cause the calculations of the evolutionary tracks to be very slow. - -## 5. ROTATION AND ACTIVITY QUANTITES - -The code gives the user the ability to use the basic functions for calculating many activity related quantities as functions os stellar properties. For example, the user can calculate XUV luminosities from stellar mass, age, and rotation rates using the Lxuv function. For example, - -```python -Lxuv = mors.Lxuv(Mstar=1.0, Age=5000.0, Omega=10.0) -``` - -This gives a dictionary holding X-ray, EUV, and Lyman-alpha luminosities for a 5000 Myr old solar mass star rotating 10 times faster than the Sun. The surface rotation rate can be specified as a rotation velocity using the Omega or OmegaEnv arguments or as a rotation period in days using the Prot argument, e.g. - -```python -Lxuv = mors.Lxuv(Mstar=1.0, Age=5000.0, Prot=1.0) -``` - -This is similar to above, but for a star with a rotation period of 1 day. - -The dictionary returned contains the following luminosities, all in erg s-1 - -- Lxuv - 0.517 to 92 nm -- Lx - 0.517 to 12.4 nm (0.1 to 24 keV) -- Leuv1 - 10 to 36 nm -- Leuv2 - 36 to 92 nm -- Leuv - 10 to 92 nm -- Lly - Lymann-alpha emission line - -The dictionary also contains surface fluxes (e.g. Fxuv, Fx, Feuv1,...) in erg s-1 cm-2 and luminosities normalised to bolometric luminosities (e.g Rxuv, Rx, Reuv1,...). - -The user can also just get the X-ray luminosity as a float using Lx() - -```python -Lx = mors.Lx(Mstar=1.0, Age=5000.0, Omega=10.0) -``` - -And similarly for the EUV and Lymann-alpha luminosities - -```python -Leuv = mors.Leuv(Mstar=1.0, Age=5000.0, Omega=10.0) -Lly = mors.Lly(Mstar=1.0, Age=5000.0, Omega=10.0) -``` - -The function Leuv() also takes the keyword argument 'band' which can be used to specify the band desired (=0 for 10-92 nm; =1 for 10-32 nm; =2 for 32-92 nm). - -The above function calculate an average Lx for a star given its mass, age, and rotation. In reality there is a scatter around these values that appear somewhat random, likely due to variability. This scatter can be described as a log normal probability density function. To calculate this scatter for X-ray luminosity, the user can use the XrayScatter() function. - -```python -deltaLx = mors.XrayScatter(Lx) -``` - -Here, the user should just enter the X-ray luminosity either as a single value or a numpy array and it returns the deltaLx from the equation LxScattered = Lx + deltaLx, where Lx is the value with this scatter not included and LxScattered is the value with it included. The user can also send in Fx or Rx to the function and get deltaFx and deltaRx. Note that these values are random and will be different each time. The width of the random distribution is set by sigmaXray in the parameter file and can be set using the params keyword argument. - -To get scatter values for EUV, Ly-alpha, and all other parameters returned by Lxuv() described above, use XUVScatter() which takes the dictionary returned by Lxuv(). - -```python -Lxuv = mors.Lxuv(Mstar=1.0, Age=5000.0, Omega=10.0) -deltaLxuv = mors.XUVScatter(Lxuv) -``` - -The return value is a dictionary containing delta values for the same quantities in Lxuv. - -If the user wants a more detailed set of parameters for a given star, the ExtendedQuantities() function can be used and in this case, the star's mass, age, and envelope and core rotation rates must be specified. - -```python -quantities = ExtendedQuantities(Mstar=1.0, Age=5000.0, OmegaEnv=10.0, OmegaCore=10.0) -``` - -This returns a larger dictionary with many other quantities, including some basic stellar properties such as radius and moment of inertia, the mass loss rate in the wind, the dipole field strength, and all of the torques acting on the star to influence its rotation. A list of the available parameters can be seen with - -```python -list(quantities) -``` - -## 6. STELLAR EVOLUTION QUANTITIES - -The rotation and activity evolution model requires that various basic stellar properties such as bolometric luminosity, radius, and convective turnover time, can be accessed at all ages for for all masses within the mass range considered (0.1 to 1.25 Msun). These are calculated using the evolutionary tracks fronm Spada et al. (2013) and the functions that do this are available to the user. First import the Mors package - -```python -import mors -``` - -The stellar mass for a 1.0 Msun star with an age of 1000 Myr can be calculated using - -```python -Rstar = mors.Rstar(1.0, 1000.0) -``` - -The functions available are - -- Rstar - radius (Rsun) -- Lbol - bolometric luminosity (Lsun) -- Teff - effective temperature (K) -- Itotal - total moment of inertia in (g cm2) -- Icore - core moment of inertia in (g cm2) -- Ienv - envelope moment of inertia in (g cm2) -- Mcore - core mass in (Msun) -- Menv - envelope mass in (Msun) -- Rcore - core radius in (Rsun) -- tau - convective turnover time (days) -- dItotaldt - rate of change of total moment of inertia (g cm2 Myr-1) -- dIcoredt - rate of change of core moment of inertia (g cm2 Myr-1) -- dIenvdt - rate of change of envelope moment of inertia (g cm2 Myr-1) -- dMcoredt - rate of change of core mass (Msun Myr-1) -- dRcoredt - rate of change of core radius (Rsun Myr^-1) - -Note that core and envelope have the definitions from the rotation model, so the 'core' is not the hydrogen burning core as it would typically be defined but everything interior to the outer convective zone, and the envelope is the outer convective zone. All of these functions take stellar mass in Msun and age in Myr. Alternatively, the user can call the function Value which takes the same two inputs and then the parameter name as a string. For example - -```python -Rstar = mors.Value(1.0, 1000.0, 'Rstar') -``` - -which is equivalent to the above example. - -In all cases, multiple values for mass and age can be input using lists or numpy arrays and the functions will return numpy arrays holding the results for each input case. For example, if stellar mass is input as a 1D array or list of length 5, the result will be a length 5 numpy array. If both stellar mass and age are input as arrays, the result will be a 2-dimensional array of length len(Mstar)xlen(Age). Additionally, in the call to Value(), the string holding the parameter to calculate for can also be input as a list of strings, in which case the variable retuned will have an additional dimension with values for each of the input quantities. - -The first time one of these functions is called, the code loads all of the evolutionary tracks from the Spada et al. (2013) model. These will be written to the directors in the file SEmodels.pickle in the main directory of the evolutionary models and this file will be used to load the models in the future. This file can be safely deleted if the user wants since this will just cause the code to remake it next time it is needed. The code then does a linear interpolation between mass and age bins to get values at the input masses and ages. This can be time consuming, especially if an interpolation between mass bins is required, which will be the case if the input age is not an integer multiple of 0.05 Msun. The user can load a full evolutionary track for a specific stellar mass using LoadTrack(). For example, to load a track for a 1.02 Msun star, use - -```python -mors.LoadTrack(1.02) -``` - -If a track for that specific mass is already loaded, this will do nothing. - -## 7. CLUSTER EVOLUTION CALCULATIONS - -The code allows the user to calculate the evolution of stellar clusters in addition to single stars. This is done using the Cluster class. An instance of the Cluster class can be created in much the same way as an instance of the Star class using the same input keyword arguments. The only difference is that the masses and rotation rates of the stars should be given as arrays or lists - -```python -Mstar = np.array([1.0, 0.5, 0.75]) -Omega = np.array([ 10.0, 10.0, 10.0]) -cluster = mors.Cluster(Mstar=Mstar, Omega=Omega) -``` - -This will create a cluster composed of three stars and immediately calculate evolutionary tracks for each. To see the progress of these calculations printed to the screen, use the verbose keyword argument - -```python -cluster = mors.Cluster(Mstar=Mstar, Omega=Omega, verbose=True) -``` - -As in the Star class, it is possible to specify the Age keyword argument. If this is not specified, the code will assume the Omega values are starting (1 Myr) rotation rates. If it is specified, the code will fit the rotation tracks such that they pass through the specified rotation rates at the speficied ages. If Age is specified as a single value (in Myr), it will be assumed that all stars have that age, and if it is specified as a numpy array then the array should have the same lengths as Mstar and Omega and the ages will be assumed individually for each star. As with the Star class, it is possible to input simulation parameters using the params keyword argument (see above). - -As in the Star class, an instance of the Cluster class can be saved using the Save (or save) function - -```python -cluster.Save() -``` - -By default, the data will be saved into a file called 'cluster.pickle' but using the user can specify the name o the file using the filename argument in the call to Save(). Saved clusters can be loaded using the Load() function. - -```python -cluster = mors.Load("cluster.pickle") -``` - -This is advisable since the evolutionary tracks for clusters can take a lot of time to calculate if there are lots of stars. - -The Star class instances for each star in the cluster is held in the stars list, which is an attribute of this class. To plot Lx tracks for each star for example, the user can use - -```python -plt.plot(cluster.stars[0].AgeTrack, cluster.stars[0].LxTrack) -plt.plot(cluster.stars[1].AgeTrack, cluster.stars[1].LxTrack) -plt.plot(cluster.stars[2].AgeTrack, cluster.stars[2].LxTrack) -``` - -Alternatively, each star is an attribute of the class instance and has the name 'star' followed by its place in the list (starting at zero), so the above can be replaced with - -```python -plt.plot(cluster.stars0.AgeTrack, cluster.stars0.LxTrack) -plt.plot(cluster.stars1.AgeTrack, cluster.stars1.LxTrack) -plt.plot(cluster.stars2.AgeTrack, cluster.stars2.LxTrack) -``` - -To get values for each star at a given age of a given parameter, the user can use the Values() function, specifying the age in Myr and a string for the quantity to retrieve. For example, - -```python -Lx = cluster.Values(Age=100.0, Quantity='Lx') -``` - -This gives an array with Lx for each star in the cluster. Alternatively, as with the Star class, each quantity can be specified using its own function and the above is equivalent to. - -```python -Lx = cluster.Lx(100.0) -``` - -So to plot the Lx distribution for a cluster at 100 Myr, the user can use - -```python -plt.scatter(cluster.Mstar, cluster.Lx(100.0)) -``` - -In Johnstone et al. (2020), many of the results are based on a composite cluster that is often referred to in the paper as the model cluster. This is composed of measured rotation distributions from several clusters with ages of ~150 Myr evolved back to 1 Myr. -To get the masses and initial rotation rates of the model cluster, use the function ModelCluster(). - -```python -Mstar , Omega = ModelCluster() -``` - -If this model cluster is used in any research, please cite the papers listed in Table 1 of Johnstone et al. (2020) for the 150 Myr age bin as the original sources for the rotation measurements (note that the function ModelCluster does not return these measurements, but 1 Myr rotation rates for these stars derived from their measurments using the rotational evolution model of Johnstone et al. 2020). This cluster can then be evolved in the usual way - -```python -Mstar , Omega = ModelCluster() -cluster = mors.Cluster(Mstar=Mstar, Omega=Omega) -``` - -The Cluster class also has a function that allows the user to find where in the distribution a star with a given mass and surface rotation rate rate is at a given age. This uses the Percentile function discussed above applied to the rotation distribution of this cluster at the specified age. - -## 8. HABITABLE ZONE BOUNDARIES - -Using the formulae of Kopparapu et al. (2013) and the luminosities and effective temperatures from the stellar models of Spada et al. (2013), the user can calculate the orbital distances of the habitable zone boundaries as a function of stellar mass and age. Please cite these two papers if using the output of this function. The HZ boundaries are calculated using the aOrbHZ function. - -```python -aOrbHZ = mors.aOrbHZ(Mstar=1.0) -``` - -In this case, the function returns a dictionary holding the orbital distances in AU in a dictionary. The six values in the dictionary are 'RecentVenus', 'RunawayGreenhouse', 'MoistGreenhouse', 'MaximumGreenhouse', 'EarlyMars', and 'HZ'. While the first five are obvious and correspond to the boundaries in Kopparapu et al. (2013), the final one is defined in Johnstone et al. (2020) as the average of the runaway greenhouse and moist greenhouse orbital distances. For example, to see these values - -```python -print(aOrbHZ['RecentVenus']) -print(aOrbHZ['RunawayGreenhouse']) -print(aOrbHZ['MoistGreenhouse']) -print(aOrbHZ['MaximumGreenhouse']) -print(aOrbHZ['EarlyMars']) -print(aOrbHZ['HZ']) -``` - -Mstar can be input as a numpy array in which case the dictionary will contain arrays with values for each mass. By default the orbital distances are calculated assuming stellar parameters at 5000 Myr. The user can also set the age in Myr using the Age keyword argument - -```python -aOrbHZ = mors.aOrbHZ(Mstar=1.0, Age=1000.0) -``` - -## 9. ACTIVITY LIFETIMES - -The code contains functions that calculate how long stars remain above certain acitivty thresholds. Firstly, the function ActivityLifetime() takes an evolutionary track and returns when the value of the track goes below a certain threshold. For example - -```python -AgeThreshold = mors.ActivityLifetime(Age=star.AgeTrack, Track=star.LxTrack, Threshold=1.0e28) -``` - -This will tell us when the star's Lx dropped below 1028 erg s-1. If the star crosses this threshold multiple times, only the final time will be returned. If it never goes below this threshold then the final age in the track will be returned and if it is never above this threshold then 0.0 will be returned. If the user wants to set a maximum age so that the code only looks for crossings of the threshold below this age then this can be done using the AgeMax keyword argument - -More recommended though is to use the function of the same name in the Star class, where the quantity of interest should be specified as a string. - -```python -AgeThreshold = star.ActivityLifetime(Quantity='Lx', Threshold=1.0e28) -``` - -This does the same thing. Valid options for Quantity are 'Lx', 'Fx', 'Rx', 'FxHZ', 'Leuv1', 'Feuv1', 'Reuv1', 'Feuv1HZ', 'Leuv2', 'Feuv2', 'Reuv2', 'Feuv2HZ', 'Leuv', 'Feuv', 'Reuv', 'FeuvHZ', 'Lly', 'Fly', 'Rly', and 'FlyHZ'. It is also possible here to specify AgeMax. If the user wants to know how long the star remained saturated, Threshold can be given with the string 'sat'. - -```python -AgeSaturated = star.ActivityLifetime(Quantity='Lx', Threshold='sat') -``` - -Note that when doing this, the value of Quantity should not influence the return value since all XUV quantities saturate at the same time, though it still must be set to a valid option. - -The Cluster class also contains this function and is called in the exact same way, returning this time the values for each star in the cluster as a numpy array. - -```python -AgeThreshold = cluster.ActivityLifetime(Quantity='Lx', Threshold=1.0e28) -``` -## 10. BARAFFE STELLAR EVOLUTION QUANTITIES - -The Forming Worlds MORS package also provides access to stellar evolution quantities according to the Baraffe model (Baraffe et al., 2002). The evolution track for a given star mass Mstar, between 0.01 and 1.4 Msun, can be loaded into memory with the command - -```python -baraffe = mors.BaraffeTrack(Mstar) -``` -The command performs mass interpolation (if needed) and time interpolation into a fine grid of 5e4 points. For a given time, the user can then access to the luminosity, the solar constant and the stellar radius with the following functions - -- Stellar radius in Rsun, time in yr -``` -Rstar = baraffe.BaraffeStellarRadius(time) -``` -- Bolometric flux in Lsun, time in yr -``` -Lbol = baraffe.BaraffeLuminosity(time) -``` -- Flux scaled by the star-planet distance in W m-2, time in yr, distance in AU -``` -Flux = baraffe.BaraffeSolarConstant(time, distance) -``` diff --git a/docs/CODE_OF_CONDUCT.md b/docs/Community/CODE_OF_CONDUCT.md similarity index 100% rename from docs/CODE_OF_CONDUCT.md rename to docs/Community/CODE_OF_CONDUCT.md diff --git a/docs/CONTRIBUTING.md b/docs/Community/CONTRIBUTING.md similarity index 100% rename from docs/CONTRIBUTING.md rename to docs/Community/CONTRIBUTING.md diff --git a/docs/contact.md b/docs/Community/contact.md similarity index 100% rename from docs/contact.md rename to docs/Community/contact.md diff --git a/docs/Explanations/activity.md b/docs/Explanations/activity.md new file mode 100644 index 0000000..935b798 --- /dev/null +++ b/docs/Explanations/activity.md @@ -0,0 +1,128 @@ +# High-energy emission + +All high-energy emission is computed from the instantaneous rotation state in `physicalmodel.ExtendedQuantities`. Throughout this section, surface fluxes are evaluated at the stellar surface (in erg s$^{-1}$ cm$^{-2}$) and luminosities are in erg s$^{-1}$. + +!!! note "Wavelength band definitions" + Following Johnstone et al. (2021) [^johnstone2021]: + + - **XUV**: 0.1–92 nm + - **X-ray**: 0.517–12.4 nm (2.4–0.1 keV) + - **EUV** (total): 10–92 nm + - **EUV1**: 10–36 nm + - **EUV2**: 36–92 nm + - **Ly-$\alpha$**: 121.6 nm + +--- + +## 1. X-ray emission + +The ratio $R_X = L_X / L_\mathrm{bol}$ is related to the Rossby number by a broken power law constrained using the sample of Wright et al. (2011) [^wright] with convective turnover times from Spada et al. (2013) [^spada]: + +$$R_X = \begin{cases} C_1 \, Ro^{\beta_1} & Ro \leq Ro_\mathrm{sat} \quad \text{(saturated)} \\ C_2 \, Ro^{\beta_2} & Ro > Ro_\mathrm{sat} \quad \text{(unsaturated)} \end{cases} \tag{17}$$ + +The constants $C_1$ and $C_2$ are derived from the requirement that the two power laws are equal at the saturation point ($R_{X,\mathrm{sat}} = C_1 Ro_\mathrm{sat}^{\beta_1} = C_2 Ro_\mathrm{sat}^{\beta_2}$). The fitted parameters are: + +| Parameter | Symbol | Value | +|---|---|---| +| Saturation Rossby number | $Ro_\mathrm{sat}$ | $0.0605$ | +| Saturation $R_X$ | $R_{X,\mathrm{sat}}$ | $5.135 \times 10^{-4}$ | +| Saturated power-law index | $\beta_1$ | $-0.135$ | +| Unsaturated power-law index | $\beta_2$ | $-1.889$ | + +The $R_X$ relation is shallower in the unsaturated regime than many previous estimates, consistent with the Sun being less X-ray active than other stars with similar parameters [^reinhold]. The X-ray luminosity and surface flux then follow as: + +$$L_X = R_X \cdot L_\mathrm{bol}, \qquad F_X = \frac{L_X}{4\pi R_\star^2}$$ + +Implementation: `physicalmodel._Xray`. + +### X-ray variability + +Real stellar X-ray emission varies around the average relation. The observed scatter in the $Ro$–$R_X$ distribution can be described as a log-normal centred on zero with standard deviation $\sigma = 0.359$ dex (`params['sigmaXray']`), meaning stars spend approximately 90% of their time within one standard deviation of the average [^johnstone2021]. This scatter can be sampled with `physicalmodel.XrayScatter` or `physicalmodel.XUVScatter`, which apply correlated random offsets consistently across all XUV bands. + +--- + +## 2. Coronal temperature + +Stars with higher X-ray surface fluxes have hotter coronae. The emission-measure-weighted average coronal temperature is estimated from $F_X$ following Johnstone & Güdel (2015) [^johnstone2015]: + +$$\bar{T}_\mathrm{cor} = 0.11\, F_X^{0.26} \quad (\mathrm{MK}) \tag{18}$$ + +This relation is mass-independent when expressed in surface fluxes. Since coronae dominate emission at wavelengths below $\sim$40 nm, stars with higher $F_X$ emit a larger fraction of their XUV at shorter wavelengths. Implementation: `physicalmodel._Tcor`. + +--- + +## 3. EUV emission + +EUV emission is empirically related to $F_X$ rather than to $L_X$ or $R_X$, since $F_X$ best captures the physical state of the emitting plasma [^johnstone2015]. The relations are derived from EUVE observations of nearby F, G, K, and M stars [^craig] and solar spectra. + +### EUV band 1 (10–36 nm) + +Constrained from the EUVE stellar sample using the OLS Bisector method (Johnstone et al. 2021 [^johnstone2021] Eq. 19): + +$$\log F_\mathrm{EUV,1} = 2.04 + 0.681\,\log F_X \tag{19}$$ + +which gives + +$$\frac{F_\mathrm{EUV,1}}{F_X} = 110\,F_X^{-0.319} \tag{20}$$ + +Implementation: `physicalmodel._EUV1`. + +### EUV band 2 (36–92 nm) + +Constrained using solar spectra only, considering solar values with $L_X > 10^{27}$ erg s$^{-1}$ (Johnstone et al. 2021 [^johnstone2021] Eq. 21): + +$$\log F_\mathrm{EUV,2} = -0.341 + 0.920\,\log F_\mathrm{EUV,1} \tag{21}$$ + +which gives + +$$\frac{F_\mathrm{EUV,2}}{F_\mathrm{EUV,1}} = 0.924\,F_\mathrm{EUV,1}^{-0.0798} \tag{22}$$ + +This relation is less reliable than the $F_X$–$F_\mathrm{EUV,1}$ relation since it is derived from the Sun alone, which samples only a small fraction of the parameter space. Implementation: `physicalmodel._EUV2`. + +### Total EUV + +$$L_\mathrm{EUV} = L_\mathrm{EUV,1} + L_\mathrm{EUV,2}, \qquad F_\mathrm{EUV} = F_\mathrm{EUV,1} + F_\mathrm{EUV,2}$$ + +Implementation: `physicalmodel._EUV`. + +--- + +## 4. Ly-$\alpha$ emission + +The Ly-$\alpha$ line at 121.6 nm is formed in the transition region and upper chromosphere [^avrett] and is often more luminous than the entire X-ray and EUV combined. Although most of the line is absorbed by the ISM, reconstructions of the intrinsic line flux are available for a large number of stars [^wood2005]. + +The Ly-$\alpha$ surface flux is related to $F_X$ following Wood et al. (2005) [^wood2005] and Linsky et al. (2013) [^linsky]. When expressed in surface fluxes (rather than luminosities or 1 AU fluxes), the relation becomes mass-independent (Johnstone et al. 2021 [^johnstone2021] Eq. 23): + +$$\log F_\mathrm{Ly\alpha} = 3.97 + 0.375\,\log F_X \tag{23}$$ + +which gives + +$$\frac{F_\mathrm{Ly\alpha}}{F_X} = 1.96 \times 10^4\,F_X^{-0.681} \tag{24}$$ + +where both fluxes are in erg s$^{-1}$ cm$^{-2}$. Implementation: `physicalmodel._Lymanalpha`. + +--- + +## 5. Habitable zone fluxes + +$F_X$, $F_\mathrm{EUV,1}$, $F_\mathrm{EUV,2}$, $F_\mathrm{EUV}$, and $F_\mathrm{Ly\alpha}$ are all also computed at the habitable zone distance (`FxHZ`, `Feuv1HZ`, `Feuv2HZ`, `FeuvHZ`, `FlyHZ`) and stored on every evolutionary track. The habitable zone distance is defined as half-way between the moist and maximum greenhouse limits, calculated at 5 Gyr stellar properties. See [Habitable Zone](habitablezone.md) for details. + +--- + +[^johnstone2021]: Johnstone, C. P., Bartel, M., & Güdel, M. (2021). The active lives of stars: a complete description of the rotation and XUV evolution of F, G, K, and M dwarfs. *Astronomy & Astrophysics, 649*, A96. https://doi.org/10.1051/0004-6361/202038407 + +[^wright]: Wright, N. J., Drake, J. J., Mamajek, E. E., & Henry, G. W. (2011). The stellar-activity–rotation relationship and the evolution of stellar dynamos. *The Astrophysical Journal, 743*(1), 48. https://doi.org/10.1088/0004-637X/743/1/48 + +[^spada]: Spada, F., Demarque, P., Kim, Y.-C., & Sills, A. (2013). The radius discrepancy in low-mass stars: single versus binaries. *The Astrophysical Journal, 776*(2), 87. https://doi.org/10.1088/0004-637X/776/2/87 + +[^reinhold]: Reinhold, T., Shapiro, A. I., Solanki, S. K., et al. (2020). The Sun is less active than other solar-like stars. *Science, 368*(6490), 518–521. https://doi.org/10.1126/science.aay3821 + +[^johnstone2015]: Johnstone, C. P., & Güdel, M. (2015). The coronal temperatures of solar-type stars. *Astronomy & Astrophysics, 578*, A129. https://doi.org/10.1051/0004-6361/201526164 + +[^craig]: Craig, N., Abbott, M., Finley, D., et al. (1997). The extreme ultraviolet explorer stellar spectral atlas. *The Astrophysical Journal Supplement Series, 113*(1), 131. https://doi.org/10.1086/313052 + +[^avrett]: Avrett, E. H., & Loeser, R. (2008). Models of the solar chromosphere and transition region from SUMER and HRTS observations. *The Astrophysical Journal Supplement Series, 175*(1), 229. https://doi.org/10.1086/523671 + +[^wood2005]: Wood, B. E., Redfield, S., Linsky, J. L., Müller, H.-R., & Zank, G. P. (2005). Stellar Ly-α emission lines in the Hubble Space Telescope archive. *The Astrophysical Journal Supplement Series, 159*(1), 118. https://doi.org/10.1086/430523 + +[^linsky]: Linsky, J. L., France, K., & Ayres, T. (2013). Computing intrinsic Ly-α fluxes of F5 V to M5 V stars. *The Astrophysical Journal, 766*(2), 69. https://doi.org/10.1088/0004-637X/766/2/69 \ No newline at end of file diff --git a/docs/Explanations/habitablezone.md b/docs/Explanations/habitablezone.md new file mode 100644 index 0000000..f772541 --- /dev/null +++ b/docs/Explanations/habitablezone.md @@ -0,0 +1,47 @@ +# Habitable zone boundaries + +Habitable zone boundaries are computed following Kopparapu et al. (2013) [^kopparapu] using stellar luminosities and effective temperatures from the Spada models [^spada] (`physicalmodel.aOrbHZ`). Six boundaries are returned: + +| Boundary | Key | +|---|---| +| Recent Venus | `RecentVenus` | +| Runaway Greenhouse | `RunawayGreenhouse` | +| Moist Greenhouse | `MoistGreenhouse` | +| Maximum Greenhouse | `MaximumGreenhouse` | +| Early Mars | `EarlyMars` | +| Midpoint (Moist + maximum Greenhouse) | `HZ` | + +All distances are in AU and computed by default at 5000 Myr (`params['AgeHZ']`). + +The `HZ` boundary is defined as the midpoint between the moist and maximum greenhouse limits: + +$$a_\mathrm{HZ} = \frac{1}{2}\left(a_\mathrm{MoistGreenhouse} + a_\mathrm{MaximumGreenhouse}\right)$$ + +Each boundary distance is computed from the stellar flux factor $S_\mathrm{eff}$ following Kopparapu et al. (2013), eq. 3 [^kopparapu]: + +$$a = \left(\frac{L_\mathrm{bol}}{L_\odot \cdot S_\mathrm{eff}}\right)^{1/2} \quad (\mathrm{AU})$$ + +where $S_\mathrm{eff}$ depends on the stellar effective temperature $T_\mathrm{eff}$ via a fourth-order polynomial anchored to solar values. + +## Fluxes in the habitable zone + +$F_X$, $F_\mathrm{EUV,1}$, $F_\mathrm{EUV,2}$, $F_\mathrm{EUV}$, and $F_\mathrm{Ly\alpha}$ are all computed at the `HZ` orbital distance and stored as standard quantities on every evolutionary track: + +| Track quantity | Description | +|---|---| +| `FxHZ` | X-ray flux at $a_\mathrm{HZ}$ | +| `Feuv1HZ` | EUV1 (10–36 nm) flux at $a_\mathrm{HZ}$ | +| `Feuv2HZ` | EUV2 (36–92 nm) flux at $a_\mathrm{HZ}$ | +| `FeuvHZ` | Total EUV (10–92 nm) flux at $a_\mathrm{HZ}$ | +| `FlyHZ` | Ly-$\alpha$ flux at $a_\mathrm{HZ}$ | + +These are computed as $F = L / (4\pi a_\mathrm{HZ}^2)$ for each band. + +!!! note + The habitable zone boundaries are calculated once at instantiation using stellar properties at `params['AgeHZ']` (default 5 Gyr) and held fixed throughout the evolutionary track. This reflects the interest in planets that spend billions of years in the habitable zone, for which the main-sequence stellar properties are the appropriate reference. + +--- + +[^kopparapu]: Kopparapu, R. K., Ramirez, R., Kasting, J. F., et al. (2013). Habitable zones around main-sequence stars: new estimates. *The Astrophysical Journal, 765*(2), 131. https://doi.org/10.1088/0004-637X/765/2/131 + +[^spada]: Spada, F., Demarque, P., Kim, Y.-C., & Sills, A. (2013). The radius discrepancy in low-mass stars: single versus binaries. *The Astrophysical Journal, 776*(2), 87. https://doi.org/10.1088/0004-637X/776/2/87 \ No newline at end of file diff --git a/docs/Explanations/proteus.md b/docs/Explanations/proteus.md new file mode 100644 index 0000000..683ba11 --- /dev/null +++ b/docs/Explanations/proteus.md @@ -0,0 +1,70 @@ +# Coupling to PROTEUS + +[PROTEUS](https://proteus-framework.org/PROTEUS) is a coupled planetary evolution framework that simulates the long-term evolution of rocky planets, including their interior, atmosphere, and the stellar environment they orbit in. A high-level schematic of PROTEUS components and corresponding modules can be found below. Please find a description of the PROTEUS architecture [here](https://proteus-framework.org/PROTEUS/Explanations/model.html).
+
+ +

+
+ Schematic of PROTEUS components and corresponding modules. MORS is shown in orange.
+

+ +## MORS in PROTEUS + +The host star is not static in PROTEUS simulations; its luminosity, radius, temperature, and high-energy emission all change significantly over geological timescales, and these changes directly affect the planet's atmospheric escape, photochemistry, and surface conditions. MORS is the stellar evolution module within PROTEUS responsible for tracking how the star changes over time. At each iteration of the PROTEUS simulation loop, MORS provides: + +- The **stellar radius and effective temperature** at the current age, used to compute the planetary energy budget +- The **bolometric instellation** at the planet's orbital distance, which drives the atmospheric energy balance +- The **XUV flux** (X-ray + EUV) as a function of stellar age and rotation, which drives atmospheric escape +- A **historical stellar spectrum** at the current age, constructed by scaling a modern reference spectrum using the MORS activity tracks + +MORS is selected by setting `star.module = 'mors'` in the PROTEUS configuration file. + +## Track options + +PROTEUS supports two sets of stellar evolution tracks through MORS, selected via `star.mors.tracks`: + +| Setting | Tracks | Mass range | Time unit | XUV | +|---|---|---|---|---| +| `'spada'` | Spada et al. (2013) | 0.10–1.25 Msun | Myr | Yes | +| `'baraffe'` | Baraffe et al. (2015) | 0.01–1.40 Msun | yr | No | + +!!! warning "Mass clipped outside valid range" + If the configured stellar mass falls outside the valid range for the chosen tracks, it is **silently clipped** to the nearest limit. + +## Rotation (Spada tracks only) + +For Spada tracks, the initial rotation must be specified using one of two options in the configuration: + +- `star.mors.rot_pcntle`: rotation percentile in the 1 Myr distribution. When set, the reference age is fixed at 1 Myr, consistent with the assumptions in `mors.Percentile()`. +- `star.mors.rot_period`: rotation period in days at the current stellar age (`star.mors.age_now`). + +Only one of these can be set at a time. Baraffe tracks do not use a rotation model. + +## Stellar spectrum + +PROTEUS requires a modern reference spectrum to initialise the spectral synthesis. This can be a custom stellar spectrum scaled to 1 AU, or an automatically downloaded stellar spectrum. There are three automatic options, configured via `star.mors.spectrum_source`: + +| Setting | Behaviour | +|---|---| +| `'solar'` | Use modern or historical solar reference spectrum | +| `'muscles'` | Use [MUSCLES](https://archive.stsci.edu/hlsp/muscles) observed spectrum | +| `'phoenix'` | Generate a [PHOENIX](https://phoenix.astro.physik.uni-goettingen.de/) synthetic spectrum from stellar parameters | + +PROTEUS rescales the spectrum to the planet's orbital separation. More information on stellar spectra in PROTEUS can be found [here](https://proteus-framework.org/PROTEUS/Reference/data.html#stellar-spectra). + +## Quantities updated during the simulation loop + +At each PROTEUS iteration, `update_stellar_quantities` updates the following: + +| Quantity | Spada | Baraffe | +|---|---|---| +| Stellar radius | `star.Value(age, 'Rstar')` | `BaraffeStellarRadius(age)` | +| Effective temperature | `star.Value(age, 'Teff')` | `BaraffeStellarTeff(age)` | +| Bolometric instellation | From `Lbol` track | `BaraffeSolarConstant(age, sep)` | +| XUV instellation | From `Lx + Leuv` tracks | Not available (set to 0) | + +Ages are passed in years to Baraffe methods and in Myr to Spada methods. + +## Spectral synthesis during the simulation + +For Spada tracks, historical spectra are computed by scaling the modern reference spectrum band-by-band using `mors.synthesis.CalcScaledSpectrumFromProps`. For Baraffe tracks, spectral synthesis uses `BaraffeTrack.BaraffeSpectrumCalc`, which scales the modern spectrum by the ratio of historical to modern bolometric luminosity. No band-resolved scaling is available. diff --git a/docs/Explanations/rotation.md b/docs/Explanations/rotation.md new file mode 100644 index 0000000..33cbd99 --- /dev/null +++ b/docs/Explanations/rotation.md @@ -0,0 +1,159 @@ +# Rotational evolution model + +## 1. Two-zone structure + +For stars with $M_\star \geq 0.35\,M_\odot$, the star is divided into a **radiative core** and a **convective envelope**, each treated as a solid-body rotator with its own angular velocity ($\Omega_\mathrm{core}$, $\Omega_\mathrm{env}$). Their evolution is governed by two coupled ODEs (`physicalmodel.py: RotationQuantities`): + +$$\frac{d\Omega_\mathrm{core}}{dt} = \frac{1}{I_\mathrm{core}} \left( -\tau_\mathrm{ce} - \tau_\mathrm{cg} - \Omega_\mathrm{core} \frac{dI_\mathrm{core}}{dt} \right) \tag{1}$$ + +$$\frac{d\Omega_\mathrm{env}}{dt} = \frac{1}{I_\mathrm{env}} \left( \tau_w + \tau_\mathrm{ce} + \tau_\mathrm{cg} + \tau_\mathrm{dl} - \Omega_\mathrm{env} \frac{dI_\mathrm{env}}{dt} \right) \tag{2}$$ + +where $\Omega_\mathrm{core}$ and $\Omega_\mathrm{env}$ are the core and envelope angular velocities, $I_\mathrm{core}$ and $I_\mathrm{env}$ are their respective moments of inertia, $\tau_w$ is the stellar wind spin-down torque, $\tau_\mathrm{ce}$ is the core–envelope coupling torque, $\tau_\mathrm{cg}$ is the core-growth torque, and $\tau_\mathrm{dl}$ is the disk-locking torque. + +For fully convective stars ($M_\star \lesssim 0.35\,M_\odot$), the distinction between core and envelope is not considered and the star rotates entirely as a solid body: + +$$\frac{d\Omega_\star}{dt} = \frac{1}{I_\star}\left(\tau_w + \tau_\mathrm{dl} - \Omega_\star \frac{dI_\star}{dt}\right) \tag{3}$$ + +where $\Omega_\star$ and $I_\star$ are the star's rotation rate and moment of inertia. The threshold is controlled by the parameters `MstarThresholdCE` ($0.35\,M_\odot$) and `IcoreThresholdCE` ($I_\mathrm{core}/I_\mathrm{total} < 0.01$). Stellar evolution parameters ($I_\mathrm{core}$, $I_\mathrm{env}$, $R_\star$, etc.) are taken from the models of Spada et al. (2013) [^spada]. + +--- + +## 2. Torques + +### Wind spin-down torque $\tau_w$ + +The wind torque follows Matt et al. (2012) [^matt2012]. We calculate $\tau_w = -K_\tau \tau'$, where $K_\tau = 11$ (`params['Kwind']`) is a parameter used to reproduce the Skumanich spin-down of the modern Sun, and $\tau'$ is given by: + +$$\tau' = K_1^2 B_\mathrm{dip}^{4m} \dot{M}_\star^{1-2m} R_\star^{4m+2} \frac{\Omega_\mathrm{env}}{(K_2^2 v_\mathrm{esc}^2 + \Omega_\mathrm{env}^2 R_\star^2)^m} \tag{4,5}$$ + +with $K_1 = 1.3$, $K_2 = 0.0506$, $m = 0.2177$. Here $B_\mathrm{dip}$ is the dipole field strength, $\dot{M}_\star$ is the wind mass loss rate, $R_\star$ and $M_\star$ are the stellar radius and mass, and $v_\mathrm{esc} = \sqrt{2GM_\star/R_\star}$ is the surface escape velocity (`physicalmodel._vEsc`). + +#### Dipole field strength $B_\mathrm{dip}$ + +The large-scale dipole field scales with Rossby number following Vidotto et al. (2014) [^vidotto]: + +$$B_\mathrm{dip} = \begin{cases} B_{\mathrm{dip},\odot} \left(\dfrac{Ro_\mathrm{sat}}{Ro_\odot}\right)^{-1.32} & \text{if } Ro \leq Ro_\mathrm{sat} \\[8pt] B_{\mathrm{dip},\odot} \left(\dfrac{Ro}{Ro_\odot}\right)^{-1.32} & \text{otherwise} \end{cases} \tag{6}$$ + +where $Ro_\odot$ and $B_{\mathrm{dip},\odot}$ are the Rossby number and dipole field strength of the modern Sun. The solar dipole field strength is $B_{\mathrm{dip},\odot} = 1.35\,\mathrm{G}$ (`params['BdipSun']`) and the saturation Rossby number is $Ro_\mathrm{sat} = 0.0605$ (`params['RoSatBdip']`). + +#### Wind mass loss rate $\dot{M}_\star$ + +The mass loss rate scales with Rossby number, stellar radius, and stellar mass (`physicalmodel._Mdot`): + +$$\dot{M}_\star = \begin{cases} f\,\dot{M}_\odot \left(\dfrac{R_\star}{R_\odot}\right)^2 \left(\dfrac{Ro_\mathrm{sat}}{Ro_\odot}\right)^{a_w} \left(\dfrac{M_\star}{M_\odot}\right)^{b_w} & \text{if } Ro \leq Ro_\mathrm{sat} \\[8pt] f\,\dot{M}_\odot \left(\dfrac{R_\star}{R_\odot}\right)^2 \left(\dfrac{Ro}{Ro_\odot}\right)^{a_w} \left(\dfrac{M_\star}{M_\odot}\right)^{b_w} & \text{otherwise} \end{cases} \tag{7}$$ + +where $\dot{M}_\odot = 1.4 \times 10^{-14}\,M_\odot\,\mathrm{yr}^{-1}$ is the current solar mass loss rate, $a_w = -1.7591$ and $b_w = 0.6494$ are fit parameters (`params['aMdot']`, `params['bMdot']`), and $f$ is the magnetocentrifugal factor described below. + +#### Magnetocentrifugal enhancement $f$ + +For very rapidly rotating stars approaching breakup, wind mass loss is enhanced by magnetocentrifugal effects [^johnstone2017] (`physicalmodel.MdotFactor`): + +$$f(\Omega_\mathrm{env}) = \begin{cases} 1 & x \leq 0.1 \\ 0.93\,(1.01 - x)^{-0.43}\,e^{0.31 x^{7.5}} & \text{otherwise} \end{cases} \tag{8}$$ + +where $x = \Omega_\mathrm{env}/\Omega_\mathrm{break}$. Stars reach breakup when the Keplerian co-rotation radius equals the stellar equatorial radius. Taking the polar radius $R_p = R_\star$ [^maeder], this gives: + +$$\Omega_\mathrm{break} = \left(\frac{2}{3}\right)^{3/2} \left(\frac{G M_\star}{R_\star^3}\right)^{1/2} \tag{9}$$ + +implemented in `physicalmodel.OmegaBreak`. The evolution of $\Omega_\mathrm{break}$ depends on mass and age through $R_\star$ and $M_\star$ from the stellar evolution models. + +--- + +### Core–envelope coupling torque $\tau_\mathrm{ce}$ + +Angular momentum is exchanged between core and envelope on a coupling timescale $t_\mathrm{ce}$ (`physicalmodel._torqueCoreEnvelope`), following the approach of MacGregor & Brenner (1991) [^macgregor], Spada et al. (2011) [^spada2011], and Gallet & Bouvier (2015) [^gallet]. We define this torque such that positive values imply angular momentum transfer from the core to the envelope: + +$$\tau_\mathrm{ce} = \frac{\Delta J}{t_\mathrm{ce}} \tag{10}$$ + +where $\Delta J$ is the angular momentum that must be transferred to bring both components to the same rotation rate: + +$$\Delta J = \frac{I_\mathrm{env}\,I_\mathrm{core}}{I_\mathrm{env} + I_\mathrm{core}}\,(\Omega_\mathrm{core} - \Omega_\mathrm{env}) \tag{11}$$ + +which implies $\Delta J = 0$ when $\Omega_\mathrm{core} = \Omega_\mathrm{env}$. The coupling timescale has a power-law dependence on differential rotation and stellar mass: + +$$t_\mathrm{ce} = a_\mathrm{ce}\,|\Omega_\mathrm{env} - \Omega_\mathrm{core}|^{b_\mathrm{ce}} \left(\frac{M_\star}{M_\odot}\right)^{c_\mathrm{ce}} \tag{12}$$ + +with $a_\mathrm{ce} = 25.6015$, $b_\mathrm{ce} = -3.4817 \times 10^{-2}$, $c_\mathrm{ce} = -0.4476$ (in Myr, with angular velocities in units of $\Omega_\odot$). + +--- + +### Core-growth torque $\tau_\mathrm{cg}$ + +As the radiative core grows during the pre-main sequence, material from the envelope becomes part of the core and carries its angular momentum with it (`physicalmodel._torqueCoreGrowth`). Assuming a positive value corresponds to angular momentum transport from the envelope to the core: + +$$\tau_\mathrm{cg} = -\frac{2}{3} R_\mathrm{core}^2\,\Omega_\mathrm{env}\,\frac{dM_\mathrm{core}}{dt} \tag{13}$$ + +where $M_\mathrm{core}$ and $R_\mathrm{core}$ are the core mass and radius. This torque is valid when $dM_\mathrm{core}/dt > 0$; when $dM_\mathrm{core}/dt < 0$, $\Omega_\mathrm{env}$ is replaced by $\Omega_\mathrm{core}$. + +--- + +### Disk-locking torque $\tau_\mathrm{dl}$ + +During the early pre-main-sequence phase, stars still possess circumstellar gas disks and do not spin up despite contracting [^allain][^gallet]. A disk-locking torque acting on the envelope cancels all other terms in Eq. (2) to keep the surface rotation constant (`physicalmodel._torqueDiskLocking`): + +$$\tau_\mathrm{dl} = \begin{cases} -\tau_w - \tau_\mathrm{ce} - \tau_\mathrm{cg} + \Omega_\mathrm{env}\,\dfrac{dI_\mathrm{env}}{dt} & \text{if } t \leq t_\mathrm{disk} \\ 0 & \text{otherwise} \end{cases} \tag{14}$$ + +The disk-locking timescale follows Tu et al. (2015) [^tu] and Johnstone et al. (2019) [^johnstone2019]: + +$$t_\mathrm{disk} = 13.5 \left(\frac{\Omega_0}{\Omega_\odot}\right)^{-0.5}\,\mathrm{Myr} \tag{15}$$ + +where $\Omega_0$ is the initial (1 Myr) rotation rate. The inverse dependence means fast rotators lose their disks earlier, consistent with observed rotation distributions in young clusters [^moraux]. The timescale is capped at a maximum of 15 Myr (`params['ageDLmax']`) to avoid unreasonably large values for slow rotators. + +--- + +### Moment-of-inertia change torque $\tau_\mathrm{mom}$ + +Changes in the moments of inertia due to stellar contraction and core growth contribute an effective torque (`physicalmodel._torqueMoment`). This is not a physical torque in the traditional sense but encapsulates angular momentum conservation as the stellar structure evolves: + +$$\tau_{\mathrm{env,mom}} = -\frac{dI_\mathrm{env}}{dt}\,\Omega_\mathrm{env}, \qquad \tau_{\mathrm{core,mom}} = -\frac{dI_\mathrm{core}}{dt}\,\Omega_\mathrm{core}$$ + +When core–envelope decoupling is inactive, the total moment of inertia is used instead: $\tau_{\mathrm{env,mom}} = -(dI_\mathrm{total}/dt)\,\Omega_\mathrm{env}$. + +--- + +## 3. Numerical Integration + +Rotational evolution is performed by `rotevo.EvolveRotation`, which integrates the two-component ODE system from `AgeMin` (default 1 Myr) to `AgeMax` (end of main sequence for the given mass). Five solvers are available, selected by `params['TimeIntegrationMethod']`: + +| Method | Description | +|---|---| +| `ForwardEuler` | First-order explicit (for testing only) | +| `RungeKutta4` | Classical 4th-order Runge–Kutta | +| `RungeKuttaFehlberg` | Adaptive-step RKF45 | +| `Rosenbrock` | Adaptive-step stiff solver (RODAS3) | +| `RosenbrockFixed` | Fixed-step Rosenbrock (default) | + +The default **RosenbrockFixed** solver uses a timestep that grows with age: + +$$\Delta t = 0.1 \times \mathrm{Age}^{0.75} \quad \text{(capped at } \Delta t_\mathrm{max} = 50\,\mathrm{Myr)}$$ + +This resolves the rapid early evolution without the overhead of adaptive step-size control. The Jacobian $d(\dot{\Omega})/d\Omega$ is computed by finite differences (`rotevo._JacobianRB`) and a 4-stage RODAS3 Rosenbrock scheme is applied (`rotevo._kCoeffRB`). + +### Fitting an initial rotation rate + +When the user specifies a known surface rotation rate at a known age (rather than an initial rotation rate), `rotevo.FitRotation` performs a bisection search over initial rotation rates $\Omega_0 \in [0.1,\,50]\,\Omega_\odot$ to find the evolutionary track that passes through the observed value, to within a tolerance of $10^{-5}$ and a maximum of 1000 bisection steps. + +--- + +[^spada]: Spada, F., Demarque, P., Kim, Y.-C., & Sills, A. (2013). The radius discrepancy in low-mass stars: single versus binaries. *The Astrophysical Journal, 776*(2), 87. https://doi.org/10.1088/0004-637X/776/2/87 + +[^matt2012]: Matt, S. P., MacGregor, K. B., Pinsonneault, M. H., & Greene, T. P. (2012). Magnetic braking formulation for sun-like stars: dependence on dipole field strength and mass. *The Astrophysical Journal, 754*(2), L26. https://doi.org/10.1088/2041-8205/754/2/L26 + +[^vidotto]: Vidotto, A. A., Gregory, S. G., Jardine, M., et al. (2014). Stellar magnetism: empirical trends with age and rotation. *Monthly Notices of the Royal Astronomical Society, 441*(3), 2361–2374. https://doi.org/10.1093/mnras/stu728 + +[^johnstone2017]: Johnstone, C. P. (2017). Magnetocentrifugal winds from nearly Keplerian discs. *Astronomy & Astrophysics, 598*, A24. https://doi.org/10.1051/0004-6361/201629405 + +[^maeder]: Maeder, A. (2009). *Physics, Formation and Evolution of Rotating Stars.* Springer Berlin Heidelberg. + +[^macgregor]: MacGregor, K. B., & Brenner, M. (1991). Rotational evolution of solar-type stars. I. Main-sequence evolution. *The Astrophysical Journal, 376*, 204. https://doi.org/10.1086/170269 + +[^spada2011]: Spada, F., Lanzafame, A. C., Lanza, A. F., Messina, S., & Collier Cameron, A. (2011). Modelling the rotational evolution of solar-like stars. *Monthly Notices of the Royal Astronomical Society, 416*(1), 447–456. https://doi.org/10.1111/j.1365-2966.2011.19052.x + +[^gallet]: Gallet, F., & Bouvier, J. (2015). Improved angular momentum evolution model for solar-like stars. *Astronomy & Astrophysics, 577*, A98. https://doi.org/10.1051/0004-6361/201525660 + +[^allain]: Allain, S. (1998). Modelling the angular momentum evolution of low-mass stars with core-envelope decoupling. *Astronomy & Astrophysics, 333*, 629–643. + +[^tu]: Tu, L., Johnstone, C. P., Güdel, M., & Lammer, H. (2015). The extreme ultraviolet and X-ray Sun in Time: high-energy evolutionary tracks of a solar-like star. *Astronomy & Astrophysics, 577*, L3. https://doi.org/10.1051/0004-6361/201526146 + +[^johnstone2019]: Johnstone, C. P., Khodachenko, M. L., Lüftinger, T., et al. (2019). Extreme hydrodynamic losses of Earth-like atmospheres in the habitable zones of very active stars. *Astronomy & Astrophysics, 624*, L10. https://doi.org/10.1051/0004-6361/201935279 + +[^moraux]: Moraux, E., Artemenko, S., Bouvier, J., et al. (2013). The h Per cluster: a product of disk-regulated angular momentum evolution? *Astronomy & Astrophysics, 560*, A13. https://doi.org/10.1051/0004-6361/201322042 \ No newline at end of file diff --git a/docs/Explanations/spectral_synthesis.md b/docs/Explanations/spectral_synthesis.md new file mode 100644 index 0000000..9815c0f --- /dev/null +++ b/docs/Explanations/spectral_synthesis.md @@ -0,0 +1,13 @@ +# Spectral synthesis + +The `spectrum.py` module provides the `Spectrum` class for handling continuous stellar spectra (wavelength in nm, flux in erg s$^{-1}$ cm$^{-2}$ nm$^{-1}$ at 1 AU). Spectra can be loaded from TSV files or arrays, extended to shorter wavelengths using a constant extrapolation and to longer wavelengths using the Planck function (`Spectrum.ExtendPlanck`), and integrated over the five defined wavelength bands (X-ray, EUV1, EUV2, UV, Planckian) with `Spectrum.CalcBandFluxes`. + +## Historical spectra + +The `synthesis.py` module builds historical spectra by scaling a modern reference spectrum band-by-band. The workflow consists of three steps: + +1. `GetProperties(Mstar, pctle, age)` computes band-integrated fluxes and stellar radius and temperature for a given star at a given age. +2. `CalcBandScales(modern_dict, historical_dict)` computes the flux scale factor $Q$ for each band as the ratio of historical to modern flux. +3. `CalcScaledSpectrumFromProps(modern_spec, modern_dict, historical_dict)` applies those scale factors to the modern spectrum to produce the historical one. + +An inverse fitting function `FitModernProperties` uses **Nelder-Mead optimisation** to estimate the rotation percentile (and optionally the age) of a star from its observed spectrum. \ No newline at end of file diff --git a/docs/Explanations/structure.md b/docs/Explanations/structure.md new file mode 100644 index 0000000..bc11603 --- /dev/null +++ b/docs/Explanations/structure.md @@ -0,0 +1,28 @@ +# MORS model overview + +MORS computes the rotational spin-down and high-energy (X-ray, EUV, Ly-$\alpha$) emission history of low-mass stars with masses between $0.1$ and $1.25\,M_\odot$, from 1 Myr through the end of the main sequence. The model is described in full in Johnstone, Bartel & Güdel (2021) [^johnstone2021] and consists of a rotational evolution model and a high-energy emission model, found in the sidebar. It also calculates habitable zone fluxes and builds historical spectra from modern reference spectra. + +A model parameter reference can be found [here](../Reference/parameters.md). + +## Stellar structure + +All time-dependent internal stellar properties (radius, luminosity, effective temperature, core and envelope moments of inertia and their rates of change, convective turnover time, core radius and mass) are taken from the pre-computed grids of Spada et al. (2013) [^spada]. The grid spans 24 mass bins from $0.1$ to $1.25\,M_\odot$ and is loaded once at startup by `stellarevo.StarEvo`. When a stellar mass falls between two grid points, a bilinear interpolation in mass and log-age is performed transparently by `stellarevo._ValueSingle`. + +The relevant solar calibration values hard-coded in `constants.py` are: + +| Quantity | Symbol | Value | +|---|---|---| +| Solar rotation rate | $\Omega_\odot$ | $2.67 \times 10^{-6}$ rad s$^{-1}$ | +| Solar bolometric luminosity | $L_\odot$ | $3.828 \times 10^{33}$ erg s$^{-1}$ | +| Solar convective turnover time | $\tau_\odot$ | $28.436$ days | +| Solar Rossby number | $Ro_\odot$ | $P_{\mathrm{rot},\odot} / \tau_\odot$ | + +The Rossby number is defined throughout the code as + +$$Ro = \frac{P_\mathrm{rot}}{\tau_\mathrm{conv}}$$ + +where $P_\mathrm{rot}$ is the surface rotation period in days and $\tau_\mathrm{conv}$ is the convective turnover time from the Spada models, both evaluated at the same mass and age. + +[^johnstone2021]: Johnstone, C. P., Bartel, M., & Güdel, M. (2021). The active lives of stars: a complete description of the rotation and XUV evolution of F, G, K, and M dwarfs. *Astronomy & Astrophysics, 649*, A96. https://doi.org/10.1051/0004-6361/202038407 + +[^spada]: Spada, F., Demarque, P., Kim, Y.-C., & Sills, A. (2013). The radius discrepancy in low-mass stars: single versus binaries. *The Astrophysical Journal, 776*(2), 87. https://doi.org/10.1088/0004-637X/776/2/87 \ No newline at end of file diff --git a/docs/How-to/activity_quantities.md b/docs/How-to/activity_quantities.md index f83a12e..b29f163 100644 --- a/docs/How-to/activity_quantities.md +++ b/docs/How-to/activity_quantities.md @@ -1,130 +1,133 @@ # Find stellar rotation and activity quantities -### Goal -Compute high-energy emission quantities (X-ray, EUV, Ly-α) from stellar **mass**, **age**, and **rotation**, optionally add variability/scatter, and (if needed) retrieve a larger set of model diagnostics via `ExtendedQuantities`. +Compute high-energy emission quantities (X-ray, EUV, Ly-$\alpha$) from stellar mass, age, and rotation rate, optionally add variability scatter, and retrieve detailed model diagnostics via `ExtendedQuantities`. -### Prerequisites -Install MORS and download the required stellar evolution data: +## Prerequisites + +Make sure the package and stellar evolution data are installed: ```bash pip install fwl-mors mors download all ``` -!!! Units - `Mstar` in **M☉**, `Age` in **Myr**, `Prot` in **days**, `Omega` in **Ω☉**. Luminosities are in **erg s⁻¹** and surface fluxes in **erg s⁻¹ cm⁻²**. +!!! info "Units" + `Age` is in **Myr**, `Prot` is in **days**, and `Omega` is in units of the **current solar rotation rate** ($\Omega_\odot = 2.67 \times 10^{-6}$ rad s$^{-1}$). --- -### Step 1: Get the full XUV dictionary (`Lxuv`) +## Step 1: Get the full XUV dictionary -Use `Omega` (or `OmegaEnv`) **or** `Prot` to specify the surface rotation. +Use `mors.Lxuv` with either `Omega` (Ω☉) or `Prot` (days) to specify the surface rotation: -**Using Ω (Ω☉):** ```python import mors -xuv = mors.Lxuv(Mstar=1.0, Age=5000.0, Omega=10.0) -``` -**Using rotation period (days):** -```python -import mors -xuv = mors.Lxuv(Mstar=1.0, Age=5000.0, Prot=1.0) +# using rotation rate +xuv = mors.Lxuv(Mstar=1.0, Age=5000.0, Omega=1.0) + +# using rotation period +xuv = mors.Lxuv(Mstar=1.0, Age=5000.0, Prot=25.4) ``` -The returned dictionary includes luminosities, surface fluxes, and bolometric-normalized values, e.g.: -- Luminosities: `Lxuv`, `Lx`, `Leuv1`, `Leuv2`, `Leuv`, `Lly` (erg s⁻¹) -- Fluxes: `Fxuv`, `Fx`, `Feuv1`, ... (erg s⁻¹ cm⁻²) -- Normalized: `Rxuv`, `Rx`, `Reuv1`, ... (dimensionless) +The returned dictionary contains: + +| Key | Description | Units | +|---|---|---| +| `Lxuv`, `Lx`, `Leuv`, `Leuv1`, `Leuv2`, `Lly` | Luminosities | erg s⁻¹ | +| `Fxuv`, `Fx`, `Feuv`, `Feuv1`, `Feuv2`, `Fly` | Surface fluxes | erg s⁻¹ cm⁻² | +| `Rxuv`, `Rx`, `Reuv`, `Reuv1`, `Reuv2`, `Rly` | Bolometric ratios | dimensionless | -To see what keys are present: ```python print(sorted(xuv.keys())) +print(f"Lx = {xuv['Lx']:.3e} erg/s") ``` --- -### Step 2: Retrieve a single quantity directly (`Lx`, `Leuv`, `Lly`) +## Step 2: Retrieve a single quantity directly + +Convenience functions exist for X-ray, EUV, and Ly-$\alpha$ luminosities: ```python import mors -Lx = mors.Lx(Mstar=1.0, Age=5000.0, Omega=10.0) -Leu = mors.Leuv(Mstar=1.0, Age=5000.0, Omega=10.0) -Lly = mors.Lly(Mstar=1.0, Age=5000.0, Omega=10.0) +Lx = mors.Lx(Mstar=1.0, Age=5000.0, Omega=1.0) +Lly = mors.Lly(Mstar=1.0, Age=5000.0, Omega=1.0) ``` -#### Choose an EUV sub-band with `band` -`Leuv(..., band=...)` supports: -- `band=0` → 10–92 nm (total EUV) -- `band=1` → 10–36 nm (EUV1) -- `band=2` → 36–92 nm (EUV2) +`mors.Leuv` supports a `band` argument to select the wavelength range: ```python -import mors -Leuv_total = mors.Leuv(Mstar=1.0, Age=5000.0, Omega=10.0, band=0) -Leuv1 = mors.Leuv(Mstar=1.0, Age=5000.0, Omega=10.0, band=1) -Leuv2 = mors.Leuv(Mstar=1.0, Age=5000.0, Omega=10.0, band=2) +Leuv_total = mors.Leuv(Mstar=1.0, Age=5000.0, Omega=1.0, band=0) # 10–92 nm +Leuv1 = mors.Leuv(Mstar=1.0, Age=5000.0, Omega=1.0, band=1) # 10–36 nm +Leuv2 = mors.Leuv(Mstar=1.0, Age=5000.0, Omega=1.0, band=2) # 36–92 nm ``` --- -### Step 3: Add variability/scatter (optional) +## Step 3: Add variability scatter (optional) + +MORS provides scatter functions that sample a log-normal distribution to represent stellar variability around the mean emission. -MORS provides random scatter terms intended to represent variability as a log-normal scatter. +### X-ray scatter + +Pass the mean value (any of `Lx`, `Fx`, or `Rx`) and receive a delta to add: -#### X-ray scatter (`XrayScatter`) -You pass the mean value (e.g., `Lx`) and get a delta to add: ```python import mors -Lx = mors.Lx(Mstar=1.0, Age=5000.0, Omega=10.0) +Lx = mors.Lx(Mstar=1.0, Age=5000.0, Omega=1.0) dLx = mors.XrayScatter(Lx) - Lx_scattered = Lx + dLx ``` -You can also pass `Fx` or `Rx` and receive `dFx` / `dRx`. +### Full XUV scatter + +`XUVScatter` takes the dictionary from `Lxuv` and returns a dictionary of deltas with the same keys, with correlated offsets across all bands: -#### Full XUV scatter (`XUVScatter`) -`XUVScatter` takes the dictionary from `Lxuv` and returns a dictionary of deltas with the same keys: ```python import mors -xuv = mors.Lxuv(Mstar=1.0, Age=5000.0, Omega=10.0) +xuv = mors.Lxuv(Mstar=1.0, Age=5000.0, Omega=1.0) dxuv = mors.XUVScatter(xuv) -# Example: apply deltas to get one scattered realization xuv_scattered = {k: xuv[k] + dxuv[k] for k in xuv} ``` -**Controlling the scatter width:** the X-ray scatter width is controlled by `sigmaXray` in the parameters file. You can override it by passing a custom `params` dictionary (see the parameter how-to): +### Controlling the scatter width + +The scatter width is set by `sigmaXray` (default 0.359 dex). Pass a custom parameter dictionary to both the emission and scatter functions to change it: ```python import mors -params = mors.NewParams(sigmaXray=0.3) # example value -Lx = mors.Lx(Mstar=1.0, Age=5000.0, Omega=10.0, params=params) -dLx = mors.XrayScatter(Lx, params=params) if "params" in mors.XrayScatter.__code__.co_varnames else mors.XrayScatter(Lx) +my_params = mors.NewParams(sigmaXray=0.3) + +Lx = mors.Lx(Mstar=1.0, Age=5000.0, Omega=1.0, params=my_params) +dLx = mors.XrayScatter(Lx, params=my_params) +Lx_scattered = Lx + dLx ``` -*(If `XrayScatter` does not accept `params` directly, set `sigmaXray` via your model run parameters and use it consistently.)* --- -### Step 4: Get detailed diagnostics (`ExtendedQuantities`) +## Step 4: Get detailed model diagnostics (`ExtendedQuantities`) -If you need additional model internals (stellar properties, wind quantities, magnetic fields, torques), call `ExtendedQuantities` with envelope and core rotation rates: +`ExtendedQuantities` returns a large dictionary of model internals including stellar structure properties, wind quantities, magnetic field strengths, and torques. It requires both the envelope and core rotation rates: ```python import mors -q = mors.ExtendedQuantities(Mstar=1.0, Age=5000.0, OmegaEnv=10.0, OmegaCore=10.0) -print(list(q)) +q = mors.ExtendedQuantities(Mstar=1.0, Age=5000.0, OmegaEnv=1.0, OmegaCore=1.0) +print(list(q.keys())) ``` +For a coupled core–envelope system at early ages, `OmegaEnv` and `OmegaCore` will generally differ. For a fully spun-down star, they can be set equal. + --- -### Common pitfalls -- Don’t mix `Omega` (Ω☉) and `Prot` (days) in the same call; pick one rotation representation. -- Scatter functions are random; results differ each run unless you control the random seed in your workflow. -- `ExtendedQuantities` requires **both** `OmegaEnv` and `OmegaCore`. +## Common pitfalls + +- Do not set both `Omega` and `Prot` in the same call — only one rotation representation can be used at a time. +- Scatter functions are stochastic — results differ each call unless you set a NumPy random seed. +- `ExtendedQuantities` requires both `OmegaEnv` and `OmegaCore`; unlike `Lxuv`, it does not accept `Omega` as a shorthand. \ No newline at end of file diff --git a/docs/How-to/activity_timelines.md b/docs/How-to/activity_timelines.md index 5a470c9..80c3c7d 100644 --- a/docs/How-to/activity_timelines.md +++ b/docs/How-to/activity_timelines.md @@ -1,96 +1,115 @@ -# Find stellar activity timelines +# Find stellar activity lifetimes -### Goal -Compute how long a star (or a cluster of stars) stays above an activity threshold (e.g., X-ray luminosity) or how long it remains in the saturated regime. +Compute how long a star (or cluster of stars) stays above an activity threshold, such as an X-ray luminosity limit, or how long it remains in the saturated regime. -### Prerequisites -Install MORS and download stellar evolution data: +## Prerequisites + +Make sure the package and stellar evolution data are installed: ```bash pip install fwl-mors mors download all ``` -!!! Units - Thresholds must match the quantity you use (e.g., `Lx` in **erg s⁻¹**). Ages returned are in **Myr**. +!!! info "Units" + Thresholds must match the units of the chosen quantity (e.g., `Lx` in **erg s⁻¹**). Ages returned are in **Myr**. --- -### Compute activity threshold - -#### Option 1: Use the standalone helper on an explicit track (basic) +## Option 1: Standalone helper on an explicit track -`mors.ActivityLifetime` takes an age array and a track array and returns the age when the track finally drops below the threshold. +`mors.ActivityLifetime` takes an age array and a track array directly and returns the age when the track finally drops below the threshold: ```python import mors -AgeThreshold = mors.ActivityLifetime( +star = mors.Star(Mstar=1.0, Omega=1.0) + +age_active = mors.ActivityLifetime( Age=star.AgeTrack, Track=star.LxTrack, Threshold=1.0e28 ) -print(AgeThreshold) +print(age_active) # Myr ``` -**Behavior:** -- If the track crosses the threshold multiple times → returns the **final** crossing time. -- If it never goes below the threshold → returns the **final age** in the track. -- If it is never above the threshold → returns `0.0`. +Behaviour: + +- If the track crosses the threshold multiple times, the **final** crossing age is returned. +- If the track never drops below the threshold, the **final age** in the track is returned. +- If the track is always below the threshold, `0.0` is returned. -Optional: limit the search to ages below `AgeMax`. +To limit the search to ages below a maximum: + +```python +age_active = mors.ActivityLifetime( + Age=star.AgeTrack, + Track=star.LxTrack, + Threshold=1.0e28, + AgeMax=2000.0 +) +``` --- -#### Option 2: Prefer the `Star.ActivityLifetime` method (recommended) +## Option 2: `Star.ActivityLifetime` method (recommended) -This version selects the track internally; you only provide the quantity name and threshold. +This selects the track internally — you only provide the quantity name and threshold: ```python -AgeThreshold = star.ActivityLifetime(Quantity="Lx", Threshold=1.0e28) -print(AgeThreshold) +age_active = star.ActivityLifetime(Quantity='Lx', Threshold=1.0e28) +print(age_active) # Myr ``` -You can also set a maximum age to consider: +To limit the search to ages below a maximum: ```python -AgeThreshold = star.ActivityLifetime(Quantity="Lx", Threshold=1.0e28, AgeMax=1000.0) +age_active = star.ActivityLifetime(Quantity='Lx', Threshold=1.0e28, AgeMax=1000.0) ``` -**Note**: The `Quantity` string must match one of the supported activity tracks: +The `Quantity` argument must be one of the following supported activity quantities: -- `Lx`, `Fx`, `Rx`, `FxHZ` -- `Leuv1`, `Feuv1`, `Reuv1`, `Feuv1HZ` -- `Leuv2`, `Feuv2`, `Reuv2`, `Feuv2HZ` -- `Leuv`, `Feuv`, `Reuv`, `FeuvHZ` -- `Lly`, `Fly`, `Rly`, `FlyHZ` +| Band | Valid quantities | +|---|---| +| X-ray | `Lx`, `Fx`, `Rx`, `FxHZ` | +| EUV1 (10–36 nm) | `Leuv1`, `Feuv1`, `Reuv1`, `Feuv1HZ` | +| EUV2 (36–92 nm) | `Leuv2`, `Feuv2`, `Reuv2`, `Feuv2HZ` | +| EUV (10–92 nm) | `Leuv`, `Feuv`, `Reuv`, `FeuvHZ` | +| Ly-$\alpha$ | `Lly`, `Fly`, `Rly`, `FlyHZ` | --- -### Compute cluster lifetimes (same interface, returns arrays) +## Cluster activity lifetimes -For a `mors.Cluster`, the call is the same but returns one value per star: +For a `mors.Cluster`, the same method returns one value per star as a NumPy array: ```python -AgeThreshold = cluster.ActivityLifetime(Quantity="Lx", Threshold=1.0e28) -print(AgeThreshold) # numpy array +import numpy as np +import mors + +Mstar = np.array([0.5, 0.8, 1.0, 1.2]) +Omega = np.array([1.0, 1.0, 1.0, 1.0]) +cluster = mors.Cluster(Mstar=Mstar, Omega=Omega) + +ages_active = cluster.ActivityLifetime(Quantity='Lx', Threshold=1.0e28) +print(ages_active) # array of Myr, one per star ``` --- -### Compute saturation lifetime +## Saturation lifetime -To get the time the star remains in the saturated regime, pass `Threshold="sat"`: +To find how long a star remains in the saturated X-ray regime, pass `Threshold='sat'`. This normalises the track to $\Omega_\mathrm{env}/\Omega_\mathrm{sat}$ and finds when it drops below 1, so the result is independent of which valid quantity you choose: ```python -AgeSaturated = star.ActivityLifetime(Quantity="Lx", Threshold="sat") -print(AgeSaturated) +age_saturated = star.ActivityLifetime(Quantity='Lx', Threshold='sat') +print(age_saturated) # Myr ``` -Note: the choice of `Quantity` should not change the saturation lifetime (XUV quantities saturate together), but it must be a valid option. - --- -### Common pitfalls -- Make sure your `Threshold` uses the same units as the chosen `Quantity` (e.g., `Lx` is erg s⁻¹). -- The function returns the **final** threshold crossing time if there are multiple crossings. +## Common pitfalls + +- The threshold must be in the same units as the chosen quantity (`Lx` in erg s⁻¹, `Fx` in erg s⁻¹ cm⁻², `Rx` dimensionless, etc.). +- The function always returns the **final** crossing age when there are multiple crossings. +- `Threshold='sat'` is available on `Star.ActivityLifetime` and `Cluster.ActivityLifetime`, but not on the standalone `mors.ActivityLifetime`. \ No newline at end of file diff --git a/docs/How-to/clusters.md b/docs/How-to/clusters.md index 1f0e3b8..d446396 100644 --- a/docs/How-to/clusters.md +++ b/docs/How-to/clusters.md @@ -1,9 +1,9 @@ # Calculate cluster evolution -### Goal -Compute rotation/activity evolution tracks for a **cluster of stars**, then (a) inspect per-star tracks, (b) evaluate cluster distributions at a given age, and (c) save/reload the cluster to avoid recomputation. +Compute rotation and activity evolution tracks for a cluster of stars, then inspect per-star tracks, evaluate cluster distributions at a given age, and save the result to avoid recomputation. + +## Prerequisites -### Prerequisites Install MORS and download stellar evolution data: ```bash @@ -11,14 +11,13 @@ pip install fwl-mors mors download all ``` -!!! Units - `Mstar` in **M☉**, `Age` in **Myr**, `Prot` in **days**, `Omega` in **Ω☉**. - +!!! info "Units" + `Age` is in **Myr**, `Prot` is in **days**, and `Omega` is in units of the **current solar rotation rate** ($\Omega_\odot = 2.67 \times 10^{-6}$ rad s$^{-1}$). --- -### Step 1: Create a cluster from arrays +## Step 1: Create a cluster from arrays -Create arrays/lists of masses and rotation rates (same length). If `Age` is omitted, MORS interprets `Omega` as the **initial (~1 Myr)** rotation rate. +Create arrays of masses and rotation rates of the same length. If `Age` is omitted, `Omega` is interpreted as the initial (~1 Myr) rotation rate. ```python import numpy as np @@ -30,7 +29,7 @@ Omega = np.array([10.0, 10.0, 10.0]) cluster = mors.Cluster(Mstar=Mstar, Omega=Omega) ``` -To show progress while tracks are computed, enable `verbose`: +To print progress while tracks are computed, enable `verbose`: ```python cluster = mors.Cluster(Mstar=Mstar, Omega=Omega, verbose=True) @@ -38,96 +37,96 @@ cluster = mors.Cluster(Mstar=Mstar, Omega=Omega, verbose=True) --- -### Step 2: Fit tracks through a specified age (optional) - -If you provide `Age`, MORS fits tracks so that each star passes through the given rotation rate at that age. - -**One age applied to all stars** -```python -import numpy as np -import mors +## Step 2: Fit tracks through a specified age (optional) -Mstar = np.array([1.0, 0.5, 0.75]) -Omega = np.array([50.0, 30.0, 40.0]) +If you provide `Age`, MORS fits each track so that the star passes through the given rotation rate at that age. -cluster = mors.Cluster(Mstar=Mstar, Omega=Omega, Age=100.0) # Myr -``` +**One age applied to all stars:** -**Different ages for each star** ```python -import numpy as np -import mors +cluster = mors.Cluster(Mstar=Mstar, Omega=Omega, Age=100.0) +``` -Mstar = np.array([1.0, 0.5, 0.75]) -Omega = np.array([50.0, 30.0, 40.0]) -Age = np.array([80.0, 120.0, 100.0]) # Myr; same length as Mstar/Omega +**A different age for each star:** +```python +Age = np.array([80.0, 120.0, 100.0]) # same length as Mstar and Omega cluster = mors.Cluster(Mstar=Mstar, Omega=Omega, Age=Age) ``` +!!! warning + Fitting with `Age` and `Omega` can fail if the requested rotation rate is outside the achievable range for a given mass and age. See [Troubleshooting](troubleshooting.md) for the specific error messages. + --- -### Step 3: Access per-star tracks +## Step 3: Access per-star tracks -Each star is a `mors.Star` instance stored in `cluster.stars`. +Each star is a `mors.Star` instance stored in `cluster.stars`. Stars are also accessible as attributes `cluster.star0`, `cluster.star1`, etc.: ```python import matplotlib.pyplot as plt -plt.plot(cluster.stars[0].AgeTrack, cluster.stars[0].LxTrack) -plt.plot(cluster.stars[1].AgeTrack, cluster.stars[1].LxTrack) -plt.plot(cluster.stars[2].AgeTrack, cluster.stars[2].LxTrack) -plt.show() -``` - -Some versions also expose `stars0`, `stars1`, ... as attributes: +fig, ax = plt.subplots() +for i in range(cluster.nStars): + ax.plot(cluster.stars[i].AgeTrack, cluster.stars[i].LxTrack) -```python -plt.plot(cluster.stars0.AgeTrack, cluster.stars0.LxTrack) +ax.set_xlabel("Age [Myr]") +ax.set_ylabel("Lx [erg/s]") +ax.set_xscale('log') +ax.set_yscale('log') +plt.show() ``` --- -### Step 4: Get cluster values at a fixed age +## Step 4: Get cluster values at a fixed age -Use `Values(Age=..., Quantity=...)` to retrieve an array across the cluster: +Use `cluster.Values(Age=..., Quantity=...)` to retrieve an array across all stars: ```python -Lx = cluster.Values(Age=100.0, Quantity="Lx") +Lx = cluster.Values(Age=100.0, Quantity='Lx') ``` -Or use the convenience method named after the quantity: +Or use the quantity accessor directly: ```python Lx = cluster.Lx(100.0) ``` -Plot a distribution (e.g., Lx vs mass) at a given age: +For example, to plot $L_X$ versus mass at a given age: ```python import matplotlib.pyplot as plt -plt.scatter(cluster.Mstar, cluster.Lx(100.0)) -plt.xlabel("Mstar [Msun]") -plt.ylabel("Lx [erg/s]") + +fig, ax = plt.subplots() +ax.scatter(cluster.Mstar, cluster.Lx(100.0)) +ax.set_xlabel(r"$M_\star$ [$M_\odot$]") +ax.set_ylabel(r"$L_X$ [erg s$^{-1}$]") +ax.set_yscale('log') plt.show() ``` --- -### Step 5: Save and reload (recommended for large clusters) +## Step 5: Save and reload -Cluster calculations can be expensive for many stars, so saving is recommended. +Cluster calculations can be expensive for large numbers of stars. Save and reload with: ```python +import mors + cluster.Save(filename="cluster.pickle") + +# Always use mors.Load rather than pickle.load directly, +# as it re-attaches the quantity accessor functions cluster2 = mors.Load("cluster.pickle") ``` --- -### Step 6: Evolve the built-in “model cluster” (optional) +## Step 6: Use the built-in model cluster (optional) -MORS includes a composite “model cluster” distribution (derived from observed clusters at ~150 Myr evolved back to 1 Myr). You can load it and evolve it like any other cluster: +MORS includes a model cluster distribution derived from observed young cluster rotation measurements. Load and evolve it like any other cluster: ```python import mors @@ -136,12 +135,18 @@ Mstar, Omega = mors.ModelCluster() cluster = mors.Cluster(Mstar=Mstar, Omega=Omega) ``` -!!! Citation - If you use this model cluster distribution in research, cite the rotation-measurement sources listed in [Johnstone et al. (2021), Table 1](https://www.aanda.org/articles/aa/pdf/2021/05/aa38407-20.pdf#page=4) (150 Myr bin), in addition to the MORS model paper(s). +!!! note "Citation" + If you use this model cluster distribution in published research, cite the rotation measurement sources listed in Table 1 of Johnstone et al. (2021) [^johnstone2021] in addition to the MORS paper. + +--- + +## Common pitfalls + +- `Mstar`, `Omega`, and `Age` (if provided) must all be the same length. +- `Age` is always in **Myr**; `Omega` is in **Ω☉**, not rad s$^{-1}$. +- Do not use `pickle.load` directly to reload a saved cluster — use `mors.Load` instead. +- Per-star attributes are named `cluster.star0`, `cluster.star1`, etc. (not `cluster.stars0`). --- -### Common pitfalls -- Arrays for `Mstar`, `Omega` (and `Age` if provided) must have matching lengths. -- `Age` is in **Myr**; `Omega` is in **Ω☉**. -- Fitting tracks using `Age` + `Omega` can be unreliable at late ages where tracks converge. +[^johnstone2021]: Johnstone, C. P., Bartel, M., & Güdel, M. (2021). The active lives of stars: a complete description of the rotation and XUV evolution of F, G, K, and M dwarfs. *Astronomy & Astrophysics, 649*, A96. https://doi.org/10.1051/0004-6361/202038407 \ No newline at end of file diff --git a/docs/How-to/distribution_percentile.md b/docs/How-to/distribution_percentile.md index ad53086..1aab58d 100644 --- a/docs/How-to/distribution_percentile.md +++ b/docs/How-to/distribution_percentile.md @@ -1,24 +1,23 @@ # Using model percentiles for initial rotation -### Goal -Pick an initial rotation rate from the built-in **model rotation distribution** (Johnstone et al. 2020), inspect a star’s inferred percentile, and convert between **rotation rate and percentile** for a given stellar mass. +Pick an initial rotation rate from the built-in model rotation distribution [^johnstone2021], inspect a star's inferred percentile, and convert between rotation rate and percentile for a given stellar mass. -### Prerequisites -Make sure MORS and the stellar evolution data are installed: +## Prerequisites + +Install MORS and download stellar evolution data: ```bash pip install fwl-mors mors download all ``` -!!! Units - `Omega` is in units of the current solar rotation rate (**Ω☉**). `Prot` is in **days**. `Mstar` is in **M☉**. - +!!! info "Units" + `Age` is in **Myr**, `Prot` is in **days**, and `Omega` is in units of the **current solar rotation rate** ($\Omega_\odot = 2.67 \times 10^{-6}$ rad s$^{-1}$). --- -### Step 1: Get the built-in “model cluster” distribution +## Step 1: Get the built-in model cluster distribution -This returns arrays of stellar masses and their **1 Myr** rotation rates (derived by evolving observed cluster distributions back to 1 Myr). +This returns arrays of stellar masses and their 1 Myr rotation rates, derived by evolving observed cluster rotation distributions back to 1 Myr: ```python import mors @@ -27,29 +26,31 @@ Mstar_dist, Omega_dist = mors.ModelCluster() print(Mstar_dist.shape, Omega_dist.shape) ``` -**Citation note:** If you use the model cluster distribution directly in research, cite the rotation-measurement sources listed in **Johnstone et al. (2020), Table 1** (150 Myr bin), in addition to the MORS model paper(s). +!!! note "Citation" + If you use this distribution directly in published research, cite the rotation measurement sources listed in Table 1 of Johnstone et al. (2021) [^johnstone2021] in addition to the MORS paper. --- -### Step 2: Create a star by percentile +## Step 2: Create a star by percentile -You can set initial rotation using a numeric percentile (0–100) or the strings `'slow'`, `'medium'`, `'fast'` which map to 5th/50th/95th percentiles. +Set initial rotation using a numeric percentile (0–100) or the string shortcuts `'slow'`, `'medium'`, `'fast'`, which map to the 5th, 50th, and 95th percentiles respectively: ```python import mors -# numeric percentile -star_p5 = mors.Star(Mstar=1.0, percentile=5.0) +star_slow = mors.Star(Mstar=1.0, percentile='slow') # 5th percentile +star_medium = mors.Star(Mstar=1.0, percentile='medium') # 50th percentile +star_fast = mors.Star(Mstar=1.0, percentile='fast') # 95th percentile -# equivalent string shortcut -star_slow = mors.Star(Mstar=1.0, percentile="slow") +# or with a numeric value +star = mors.Star(Mstar=1.0, percentile=10.0) ``` --- -### Step 3: Read back the star’s inferred percentile +## Step 3: Read back the star's inferred percentile -Regardless of how you create the star (Omega/Prot/percentile), MORS stores the inferred percentile after computing tracks: +Regardless of how the star is created (`Omega`, `Prot`, or `percentile`), MORS stores the inferred percentile in the 1 Myr distribution after computing the tracks: ```python print(star_slow.percentile) @@ -57,52 +58,50 @@ print(star_slow.percentile) --- -### Step 4: Convert a rotation rate (Ω or Prot) to a percentile +## Step 4: Convert a rotation rate to a percentile -**From Ω (Ω☉) to percentile** ```python import mors + +# from Omega (Ω☉) p = mors.Percentile(Mstar=1.0, Omega=10.0) print(p) -``` -**From Prot (days) to percentile** -```python -import mors +# from Prot (days) p = mors.Percentile(Mstar=1.0, Prot=1.0) print(p) ``` --- -### Step 5: Convert a percentile to a rotation rate +## Step 5: Convert a percentile to a rotation rate ```python import mors -Omega_p10 = mors.Percentile(Mstar=1.0, percentile=10.0) # returns Ω in Ω☉ -print(Omega_p10) + +Omega = mors.Percentile(Mstar=1.0, percentile=10.0) # returns Ω in Ω☉ +print(Omega) ``` --- -### Step 6: Control the mass window used for percentiles (`dMstarPer`) +## Step 6: Control the mass window used for percentiles -Percentiles are computed using stars within a mass window around `Mstar`. The width is controlled by `dMstarPer` (default: 0.1 M☉). To change it, modify parameters and pass them into `Star`: +Percentiles are computed from stars within a mass window around `Mstar`. The half-width is controlled by `dMstarPer` (default: $0.1\,M_\odot$). To change it, create a custom parameter dictionary: ```python import mors -params = mors.NewParams(dMstarPer=0.05) -star = mors.Star(Mstar=1.0, percentile="medium", params=params) +my_params = mors.NewParams(dMstarPer=0.05) +star = mors.Star(Mstar=1.0, percentile='medium', params=my_params) ``` --- -### Step 7: Use a custom rotation distribution (optional) +## Step 7: Use a custom rotation distribution (optional) -If you have your own mass+rotation distribution (instead of the built-in 1 Myr model distribution), pass it into `Percentile`. +Pass your own mass and rotation arrays into `Percentile` instead of using the built-in 1 Myr distribution: -**Using Ω distribution** ```python import numpy as np import mors @@ -114,11 +113,9 @@ p = mors.Percentile(Mstar=1.0, Omega=6.0, MstarDist=MstarDist, OmegaDist=OmegaDi print(p) ``` -**Using Prot distribution** -```python -import numpy as np -import mors +You can also supply a period distribution instead: +```python MstarDist = np.array([1.0, 1.0, 0.9, 1.1]) ProtDist = np.array([12.0, 6.0, 9.0, 7.0]) # days @@ -128,7 +125,12 @@ print(p) --- -### Common pitfalls -- Percentiles here refer to the **initial (~1 Myr)** distribution used by the model cluster. -- `Omega` is **Ω☉**, not rad/s. `Prot` is **days**. -- If your custom distribution is sparse or covers a narrow mass range, percentile estimates can become unstable. +## Common pitfalls + +- Percentiles refer to the **initial (~1 Myr)** rotation distribution used by the model cluster. +- `Omega` is in **Ω☉**, not rad s$^{-1}$. `Prot` is in **days**. +- If the custom distribution is sparse or covers a narrow mass range, at least two stars must fall within the `dMstarPer` window, otherwise `Percentile` will raise an exception. + +--- + +[^johnstone2021]: Johnstone, C. P., Bartel, M., & Güdel, M. (2021). The active lives of stars: a complete description of the rotation and XUV evolution of F, G, K, and M dwarfs. *Astronomy & Astrophysics, 649*, A96. https://doi.org/10.1051/0004-6361/202038407 \ No newline at end of file diff --git a/docs/How-to/documentation.md b/docs/How-to/documentation.md new file mode 100644 index 0000000..e53662c --- /dev/null +++ b/docs/How-to/documentation.md @@ -0,0 +1,98 @@ +# Documentation development + +This page explains how to build, preview, and extend the MORS documentation locally. + +## Overview + +MORS' documentation is written in [Markdown](https://www.markdownguide.org/basic-syntax/) and generated using [Zensical](https://zensical.org/docs/get-started/). + +Zensical is fully compatible with the project's `mkdocs.yml` configuration, which is used for the documentation build process and site structure. + +The documentation source files live in the `docs/` directory of the repository. + +## Local development + +To build and preview the documentation locally, install the documentation dependencies and start the development server: + +```console +pip install -e '.[docs]' +zensical serve +``` + +This command will generate the documentation and start a local server for previewing changes as you edit. + +Once the server is running, open the URL shown in your terminal in a browser. In most cases, it will look similar to: + +```console +http://127.0.0.1:8000 +``` +!!! note "Using a different port" + If port 8000 is already busy, you can build the documentation on a different port: + ```console + zensical serve -a localhost:8001 + ``` + +!!! note "SSH connection" + If you are connected to a remote server via SSH, for example on the Habrok, Snellius, or Kapteyn cluster, you may need to create an additional SSH tunnel. On your **local machine**, run: + + ```console + ssh -L 4000:localhost:8000 account@server + ``` + + Then open `http://localhost:4000` in your browser. + +## Documentation structure + +Documentation source files are located in the [`docs`](https://github.com/FormingWorlds/MORS/tree/main/docs) directory, and mostly follow the [Diátaxis](https://diataxis.fr/) approach of documentation. This means they are categorised into: + +- **Tutorials**: learning-oriented guides for new users, such as a first workflow tutorial. +- **How-to guides**: task-oriented instructions, such as installation, configuration, or test development. +- **Explanations**: conceptual material, such as model overviews or code architecture. +- **Reference**: lookup material, such as reference data, API-like documentation, or bibliographies. + +Using this structure helps keep pages focused on a single purpose. When adding a new page, try to decide first which of these four categories it belongs to. + +## Layout of the `docs/` directory + +Within the `docs/` directory, documentation pages are generally organised according to the categories above. In addition, several extra directories contain assets and project-level documentation material. + +The current `docs/` folder structure is shown below: + +```text +. +├── assets # Images, diagrams and logos +├── Community # Contact, contributing, code of conduct +├── Explanations # Explanation pages such as a model overview +├── getting_started.md # Getting started page with quicklinks +├── How-to # How-to guides such as this one +├── index.md # Landing page (home) +├── javascripts # Javascripts to configure features such as the clickable PROTEUS logo +├── overrides # HTML overrides on top of the Zensical configurable style +├── Reference # Reference pages, such as an API reference +│ └── api +├── stylesheets # CSS overrides on top of the Zensical configurable style +├── proteus_framework.md # Overview of PROTEUS and links to other modules +└── Tutorials # MORS tutorials +``` + +## Adding a new page + +When adding a new documentation page: + +1. Create the Markdown file in the appropriate location under `docs/`. +2. Add the page to the navigation in `mkdocs.yml` under the `nav` section. +3. Start the local preview with `zensical serve`. If the preview is already open, saving `mkdocs.yml` should refresh the page (check this in the terminal). +4. Check that the page renders correctly and appears in the expected place in the navigation. +5. Verify that internal links, images, and code blocks display correctly. + +For example, a page stored at `docs/Tutorials/model_sun.md` might be added to `mkdocs.yml` like this: + +```yaml +nav: + - Tutorials: + - Simulate the Sun: Tutorials/model_sun.md + +``` + +!!! info "Documentation files are relative to `docs/` directory" + Zensical looks for documentation files relative to the `docs/` directory. In `mkdocs.yml`, add files like this: `Tutorials/model_sun.md` and not like this: `docs/Tutorials/model_sun.md`. \ No newline at end of file diff --git a/docs/How-to/evolution.md b/docs/How-to/evolution.md index 50417b6..4337832 100644 --- a/docs/How-to/evolution.md +++ b/docs/How-to/evolution.md @@ -1,9 +1,9 @@ # Calculate a star's evolution -### Goal -Compute a star’s rotation and activity evolution tracks, then (a) plot a quantity, (b) query values at specific ages, and (c) save the result for reuse. +Compute a star's rotation and activity evolution tracks, then plot a quantity, query values at specific ages, and save the result for reuse. + +## Prerequisites -### Prerequisites Make sure the package and stellar evolution data are installed: ```bash @@ -11,87 +11,119 @@ pip install fwl-mors mors download all ``` -!!! Units - `Age` is in **Myr**, `Prot` is in **days**, and `Omega` is in units of the **current solar rotation rate (Ω☉)**. +!!! info "Units" + Throughout MORS, `Age` is in **Myr**, `Prot` is in **days**, and `Omega` is in units of the **current solar rotation rate** ($\Omega_\odot = 2.67 \times 10^{-6}$ rad s$^{-1}$). --- -### Step 1: Create a `Star` +## Step 1: Create a `Star` -Pick **one** of these common ways: +Pick one of the following ways to specify the initial rotation: -**A) Set initial rotation using Ω (at ~1 Myr)** +**A) Set initial rotation rate $\Omega$ (at ~1 Myr)** ```python import mors star = mors.Star(Mstar=1.0, Omega=10.0) ``` -**B) Set initial rotation using rotation period (days)** +**B) Set initial rotation period in days** ```python import mors star = mors.Star(Mstar=1.0, Prot=2.7) ``` -**C) Fit a track through a point (Age, Ω)** +**C) Set rotation percentile in the 1 Myr distribution** + +```python +import mors +star = mors.Star(Mstar=1.0, percentile=50.0) # median rotator +# string shortcuts are also accepted: +star = mors.Star(Mstar=1.0, percentile='slow') # 5th percentile +star = mors.Star(Mstar=1.0, percentile='medium') # 50th percentile +star = mors.Star(Mstar=1.0, percentile='fast') # 95th percentile +``` + +**D) Fit a track through a known rotation at a known age** ```python import mors star = mors.Star(Mstar=1.0, Age=100.0, Omega=50.0) ``` -Use (C) only at ages where rotation tracks have not strongly converged for that mass; otherwise the fit may be ill-posed or fail if the requested rotation rate is unrealistic. +!!! warning + Option D will raise an exception if the requested rotation rate is outside the range achievable for that mass and age (below the slowest or above the fastest possible track). See [Troubleshooting](troubleshooting.md) for the specific error messages. --- -### Step 2: Inspect available tracks and units +## Step 2: Inspect available quantities and units + +```python +star.PrintAvailableTracks() # requires logging to be configured +print(star.Units['Lx']) # prints the units string for Lx +``` + +To configure logging first: ```python +from mors.logs import setup_logger +setup_logger("INFO") star.PrintAvailableTracks() -print(star.Units['Lx']) ``` --- -### Step 3: Plot an evolutionary track +## Step 3: Plot an evolutionary track -Tracks are stored in `star.Tracks` and also exposed as `Track` arrays. +Tracks are stored in `star.Tracks` and also exposed as `star.Track` arrays: ```python import matplotlib.pyplot as plt -plt.plot(star.Tracks['Age'], star.Tracks['Lx']) -# or: plt.plot(star.AgeTrack, star.LxTrack) -plt.xlabel(f"Age [{star.Units['Age']}]") -plt.ylabel(f"Lx [{star.Units['Lx']}]") +fig, ax = plt.subplots() +ax.plot(star.AgeTrack, star.LxTrack) +ax.set_xlabel(f"Age [{star.Units['Age']}]") +ax.set_ylabel(f"Lx [{star.Units['Lx']}]") +ax.set_xscale('log') +ax.set_yscale('log') plt.show() ``` --- -### Step 4: Find a value at a given age +## Step 4: Query a value at a specific age ```python -print(star.Value(150.0, 'Lx')) -# or: -print(star.Value(Age=150.0, Quantity='Lx')) -# or (preferred when available): -print(star.Lx(150.0)) +# Using the generic Value method +lx = star.Value(Age=150.0, Quantity='Lx') + +# Using the quantity accessor (preferred) +lx = star.Lx(Age=150.0) ``` +Both return a linearly interpolated scalar at the requested age. The age must be within the range of the evolutionary track. + --- -### Step 5: Save and reload +## Step 5: Save and reload ```python +import mors + star.Save(filename="star.pickle") + +# Always use mors.Load rather than pickle.load directly, +# as it re-attaches the quantity accessor functions star2 = mors.Load("star.pickle") +print(star2.Lx(Age=4500.0)) ``` --- -### Common pitfalls -- `Age` is in **Myr** (not years). -- `Omega` is in **Ω☉** (not rad/s). -- Fitting with `Age=...` + `Omega=...` may fail at late ages or for extreme rotation rates. +## Common pitfalls + +- `Age` is always in **Myr**, not years or Gyr. +- `Omega` is in units of $\Omega_\odot$, not rad s$^{-1}$. +- Do not use `pickle.load` directly to reload a saved star — use `mors.Load` instead. +- `PrintAvailableTracks` uses the logging system and produces no output unless logging is configured. \ No newline at end of file diff --git a/docs/How-to/habitablezone.md b/docs/How-to/habitablezone.md index a6cd535..e59cf28 100644 --- a/docs/How-to/habitablezone.md +++ b/docs/How-to/habitablezone.md @@ -1,27 +1,27 @@ # Find habitable zone boundaries -### Goal -Compute habitable zone (HZ) boundary orbital distances (AU) as a function of stellar **mass** and **age** using `mors.aOrbHZ`. +Compute habitable zone (HZ) boundary orbital distances in AU as a function of stellar mass and age using `mors.aOrbHZ`. -### Prerequisites -Install MORS and download stellar evolution data: +## Prerequisites + +Make sure the package and stellar evolution data are installed: ```bash pip install fwl-mors mors download all ``` -!!! Units - **Units:** `Mstar` in **M☉**, `Age` in **Myr**, returned distances in **AU**. +!!! info "Units" + `Age` is in **Myr**, `Prot` is in **days**, and `Omega` is in units of the **current solar rotation rate** ($\Omega_\odot = 2.67 \times 10^{-6}$ rad s$^{-1}$). -!!! Citation - If you use these HZ boundaries in research, cite [Kopparapu et al. (2013)](../Reference/publications.md#bibliography) (HZ prescription) and [Spada et al. (2013)](../Reference/publications.md#bibliography)(stellar parameters used). +!!! note "Citation" + If you use these HZ boundaries in published research, cite Kopparapu et al. (2013) [^kopparapu] for the HZ prescription and Spada et al. (2013) [^spada] for the stellar parameters used. --- -### Step 1: Compute HZ boundaries for a single star mass +## Step 1: Compute HZ boundaries for a single stellar mass -By default, `aOrbHZ` uses stellar parameters at **5000 Myr** if you don’t specify `Age`. +By default, `aOrbHZ` uses stellar parameters at 5000 Myr if `Age` is not specified: ```python import mors @@ -29,39 +29,38 @@ import mors hz = mors.aOrbHZ(Mstar=1.0) ``` -The returned dictionary contains these keys: -- `RecentVenus` -- `RunawayGreenhouse` -- `MoistGreenhouse` -- `MaximumGreenhouse` -- `EarlyMars` -- `HZ` +The returned dictionary contains six boundary distances: -Print them: +| Key | Description | +|---|---| +| `RecentVenus` | Inner edge (recent Venus limit) | +| `RunawayGreenhouse` | Inner edge (runaway greenhouse) | +| `MoistGreenhouse` | Inner edge (moist greenhouse) | +| `MaximumGreenhouse` | Outer edge (maximum greenhouse) | +| `EarlyMars` | Outer edge (early Mars limit) | +| `HZ` | Midpoint of `MoistGreenhouse` and `MaximumGreenhouse` | ```python -for k in ["RecentVenus", "RunawayGreenhouse", "MoistGreenhouse", - "MaximumGreenhouse", "EarlyMars", "HZ"]: - print(k, hz[k]) +for key, val in hz.items(): + print(f"{key}: {val:.4f} AU") ``` -`HZ` is defined (Johnstone et al. 2020) as the average of the `RunawayGreenhouse` and `MoistGreenhouse` orbital distances. - --- -### Step 2: Specify a stellar age +## Step 2: Specify a stellar age ```python import mors -hz_1gyr = mors.aOrbHZ(Mstar=1.0, Age=1000.0) # Myr -print(hz_1gyr["HZ"]) + +hz = mors.aOrbHZ(Mstar=1.0, Age=1000.0) # 1 Gyr +print(hz["HZ"]) ``` --- -### Step 3: Compute HZ boundaries for multiple masses +## Step 3: Compute HZ boundaries for multiple masses -If you pass an array of masses, you get arrays back in the dictionary: +Pass an array of masses to get arrays back in the dictionary: ```python import numpy as np @@ -70,12 +69,34 @@ import mors masses = np.array([0.3, 0.5, 0.8, 1.0]) hz = mors.aOrbHZ(Mstar=masses, Age=5000.0) -print(hz["HZ"]) # array of AU +print(hz["HZ"]) # array of HZ midpoints in AU print(hz["RunawayGreenhouse"]) ``` --- -### Common pitfalls -- `Age` is in **Myr** (not years). -- When passing arrays for `Mstar`, each returned dict entry becomes an array of the same length. +## Step 4: Access HZ boundaries from a `Star` object + +When a `Star` is created, HZ boundaries are automatically computed and stored as `star.aOrbHZ`: + +```python +import mors + +star = mors.Star(Mstar=1.0, Omega=1.0) +print(star.aOrbHZ["HZ"]) +print(star.aOrbHZ["MoistGreenhouse"]) +``` + +--- + +## Common pitfalls + +- `Age` is in **Myr**, not years or Gyr. +- The `HZ` key is the midpoint of `MoistGreenhouse` and `MaximumGreenhouse`, not `RunawayGreenhouse`. +- When passing an array for `Mstar`, each value in the returned dictionary is an array of the same length. + +--- + +[^kopparapu]: Kopparapu, R. K., Ramirez, R., Kasting, J. F., et al. (2013). Habitable zones around main-sequence stars: new estimates. *The Astrophysical Journal, 765*(2), 131. https://doi.org/10.1088/0004-637X/765/2/131 + +[^spada]: Spada, F., Demarque, P., Kim, Y.-C., & Sills, A. (2013). The radius discrepancy in low-mass stars: single versus binaries. *The Astrophysical Journal, 776*(2), 87. https://doi.org/10.1088/0004-637X/776/2/87 \ No newline at end of file diff --git a/docs/How-to/installation.md b/docs/How-to/installation.md index 9224600..e68b54a 100644 --- a/docs/How-to/installation.md +++ b/docs/How-to/installation.md @@ -1,78 +1,133 @@ -# Installation +# Installation -!!! note - The standard way of installing this version of MORS is within the PROTEUS Framework, as described in the [PROTEUS installation guide](https://proteus-framework.org/PROTEUS/installation.html#9-install-submodules-as-editable). +!!! note "PROTEUS users" + The standard way of installing this version of MORS is within the PROTEUS Framework, as described in the [PROTEUS installation guide](https://proteus-framework.org/PROTEUS/installation.html#9-install-submodules-as-editable). When installed as part of PROTEUS, MORS is set up automatically alongside all other modules. The standalone instructions below are only needed if you want to use MORS independently of PROTEUS. -### Prerequisites -- **Python:** >3.11 installed -- **pip:** available (`python -m pip --version`) -- **Git:** only needed for the developer install (`git --version`) -- **Internet access:** required once to download the stellar evolution tracks -- *(Optional)* **Conda/Anaconda/Miniconda:** only if you want to use a conda environment +!!! info "Prerequisites" + - **Python** ≥ 3.11 + - **pip** (`python -m pip --version`) + - **Git**: only needed for the developer install (`git --version`) + - **Internet access**: required once to download the stellar evolution tracks + - *(Optional)* **Conda** or **venv** : recommended to isolate the installation -### 0. Optional: Conda/virtual environment +--- -Create and activate a Conda environment (requires `conda` installed): -```bash -conda create -n mors python=3.11 -y -conda activate mors +## Standard install + +MORS is available on [PyPI](https://pypi.org/project/fwl-mors/). Install it with: + +```sh +pip install fwl-mors ``` -No `conda`? create and activate a virtual environment (venv): -```bash -python -m venv .venv -source .venv/bin/activate +Then download the required stellar evolution data: + +```sh +mors download all ``` -### 1. Basic install +That's it. Jump to [first run](../Tutorials/first_run.md) to run your first model. + +--- + +## Developer install + +Use this route if you want to modify the source code or contribute to MORS. + +### 1. Create an isolated environment (recommended) -The Forming Worlds Mors package is available on PyPI. Run the following command to install +=== "Conda" + + ```sh + conda create -n mors python=3.11 -y + conda activate mors + ``` + +=== "venv" + + ```sh + python -m venv .venv + source .venv/bin/activate # Windows: .venv\Scripts\activate + ``` + +### 2. Clone the repository ```sh -pip install fwl-mors +git clone git@github.com:FormingWorlds/MORS.git +cd MORS ``` -### 2. Developer install -You can alternatively download the source code from GitHub somewhere on your computer using +### 3. Install in editable mode +```sh +pip install -e . ``` -git clone git@github.com:FormingWorlds/MORS.git + +This installs MORS and all its dependencies (see [`pyproject.toml`](https://github.com/FormingWorlds/MORS/blob/main/pyproject.toml)) while keeping the source directory live: any edits you make are immediately reflected without reinstalling. + +--- + +## Stellar evolution data + +MORS requires a set of pre-computed stellar evolution tracks. After installation, download them with: + +```sh +mors download all ``` -Then run the following command inside the main directory to install the code (check the pyproject.toml file for dependencies) +This fetches both the [Spada](https://zenodo.org/records/15729101) and [Baraffe](https://zenodo.org/records/15729114) track sets from two Zenodo records, with an automatic fallback to [OSF](https://osf.io/9u3fb/) if Zenodo is unavailable. If you only need one set, you can download them individually: +```sh +mors download spada +mors download baraffe ``` -pip install -e . + +### Data location + +By default, MORS stores data according to the [XDG Base Directory specification](https://specifications.freedesktop.org/basedir-spec/latest/). To check where data will be stored on your system: + +```sh +mors env ``` +### Changing the data directory -### 3. Stellar evolution tracks +Set the `FWL_DATA` environment variable to redirect MORS to a different location: -The code requires also a set of stellar evolution data, stored in the [OSF repository](https://osf.io/9u3fb/). +```sh +export FWL_DATA=/path/to/your/data +``` -You can use `mors download all` to download the data. This will download and extract package stellar evolution tracks data. +To make this permanent, add that line to your shell configuration file (`~/.bashrc`, `~/.zshrc`, etc.) and reload it: + +```sh +echo 'export FWL_DATA=/path/to/your/data' >> ~/.bashrc +source ~/.bashrc +``` -By default, MORS stores the data in based on the [XDG specification](https://specifications.freedesktop.org/basedir-spec/latest/). -You can check the location by typing `mors env` in your terminal. -You can override the path using the `FWL_DATA` environment variable, e.g. +Alternatively, you can pass the data path directly when loading stellar evolution tracks in Python, without setting any environment variable: -```console -export FWL_DATA=... +```python +import mors +StarEvo = mors.stellarevo.StarEvo(starEvoDir="/path/to/tracks") ``` -Where ... should be replaced with the path to your main data directory. To make this permanent on Ubuntu, use +Or when creating a `Star` object: -```console -nano ~/.bashrc +```python +star = mors.Star(Mstar=1.0, Omega=1.0, starEvoDir="/path/to/tracks") ``` -and add the export command to the bottom of the file. +--- + +## Verifying the installation -Alternatively, when creating a star object in your Python script, you can specify the path to a directory where evolution tracks are stored using the starEvoDir keyword +After installing and downloading the data, run the following to confirm everything works: ```python import mors -myStar = mors.StarEvo(starEvoDir=...) +star = mors.Star(Mstar=1.0, Omega=1.0) +print(f"Lx at 4.5 Gyr: {star.Lx(Age=4500.0):.3e} erg/s") ``` -where ... can be given as the path relative to the current directory. When this is done, no environmental variable needs to be set. \ No newline at end of file +You should see an X-ray luminosity value printed without errors. If you run into any issues, check the [troubleshooting](../How-to/troubleshooting.md) page or open an issue on [GitHub](https://github.com/FormingWorlds/MORS/issues). \ No newline at end of file diff --git a/docs/How-to/run_tests.md b/docs/How-to/run_tests.md new file mode 100644 index 0000000..e374c2f --- /dev/null +++ b/docs/How-to/run_tests.md @@ -0,0 +1,121 @@ +# Testing suite + +This page describes the current MORS test suite and how to run it. The suite covers the spectral synthesis pipeline in full, the Spectrum class, and regression tests for the Spada and Baraffe stellar evolution tracks with two ODE solvers. The physical model (`physicalmodel.py`), rotational evolution solver (`rotevo.py`), and parameter handling are not yet covered by dedicated tests. + +## Running the tests + +### Prerequisites + +Install MORS with the development dependencies: + +```bash +pip install -e .[develop] +``` + +Download the stellar evolution data (required for Spada and Baraffe track tests): + +```bash +export FWL_DATA=/path/to/your/data +mors download all +``` + +### Run the full suite + +```bash +coverage run -m pytest +``` + +### Run a specific file + +```bash +pytest tests/test_spada_RB.py +pytest tests/test_spectrum.py +``` + +### Run with verbose output + +```bash +pytest -v +``` + +### View coverage report + +```bash +coverage run -m pytest +coverage report +``` + +--- + +## Current test files + +### `test_spada_FE.py`: Spada tracks, Forward Euler solver + +Runs four parametrised integration tests using the Forward Euler ODE solver. Each test creates a `mors.Star` at a given mass and initial rotation rate and checks three quantities at a given age against regression values from a known-good run: + +| Quantity | Description | +|---|---| +| `Rstar` | Stellar radius (Rsun) | +| `Lbol` | Bolometric luminosity (erg s⁻¹) | +| `Leuv` | Total EUV luminosity (erg s⁻¹) | + +Tolerance: `rtol=1e-6`. The test calls `mors.DownloadEvolutionTracks('Spada')` to ensure data are present. + +--- + +### `test_spada_RB.py`: Spada tracks, Rosenbrock solver + +Identical structure to `test_spada_FE.py` but uses the default `RosenbrockFixed` solver. The expected values differ from the Forward Euler test because the two solvers produce slightly different numerical results at the same tolerance settings. + +--- + +### `test_baraffe.py`: Baraffe tracks + +Runs two parametrised integration tests using Baraffe et al. (2015) tracks. Each test creates a `mors.BaraffeTrack` at a given mass and checks three quantities at a given age against regression values: + +| Quantity | Description | +|---|---| +| `BaraffeLuminosity` | Bolometric luminosity (Lsun) | +| `BaraffeStellarRadius` | Stellar radius (Rsun) | +| `BaraffeSolarConstant` | Bolometric flux at given distance (W m⁻²) | + +Tolerance: `rtol=1e-5`. Note that Baraffe tracks use time in **years**, not Myr. + +--- + +### `test_spectrum.py`: Spectrum class and helpers + +Unit tests for `mors.spectrum`. No stellar evolution data required. Covers: + +- `WhichBand`: wavelength-to-band lookup, including overlap regions +- `ScaleToSurf` / `ScaleTo1AU`: round-trip scaling consistency +- `PlanckFunction_surf`: monotonically increasing with temperature, positive values +- `Spectrum.LoadDirectly`: ascending wavelength ordering, NaN/zero flux sanitisation, binwidth length +- `Spectrum.CalcBandFluxes`: band integration with constant flux (analytically verifiable) +- `Spectrum.ExtendShortwave`: extension length, wavelength bounds, constant flux value +- `Spectrum.ExtendPlanck`: extension length, wavelength bounds, positive flux +- TSV round-trip: write and reload with tolerance matching the `%1.4e` format + +--- + +### `test_synthesis.py`: Spectral synthesis + +Unit tests for `mors.synthesis`. Uses `monkeypatch` to replace `Value`, `Percentile`, `Lxuv`, `Lbol`, and `PlanckFunction_surf` with lightweight fakes, so no stellar evolution data are required. Covers: + +- `GetProperties`: flux budget consistency: $F = L / (4\pi \, \mathrm{AU}^2)$, UV remainder definition, Planck band integral +- `CalcBandScales`: scale factor $Q_k = F_k^\mathrm{hist} / F_k^\mathrm{modern}$ for each band +- `CalcScaledSpectrumFromProps`: correct band scale factor applied per wavelength, including overlap regions +- `FitModernProperties`: correct initial guess shape, correct unpacking of the `minimize` result for both fixed-age and free-age cases + +--- + +## CI matrix + +Tests run automatically on every push and pull request to `main` via GitHub Actions: + +| OS | Python versions | +|---|---| +| Ubuntu | 3.11, 3.12, 3.13 | +| macOS | 3.11, 3.12, 3.13 | + +Coverage is reported in the GitHub Actions summary. A coverage badge is generated from the `ubuntu-latest` + Python 3.12 run on `main`. \ No newline at end of file diff --git a/docs/How-to/set_parameters.md b/docs/How-to/set_parameters.md index a3e3793..3b3b562 100644 --- a/docs/How-to/set_parameters.md +++ b/docs/How-to/set_parameters.md @@ -1,101 +1,121 @@ # Setting custom simulation parameters -### Goal -Create simulation parameters for a `Star` (or `Cluster`) run, and optionally restrict the ages saved in output tracks using `AgesOut`. This overrides MORS default simulation parameters. +Create a custom parameter dictionary for a `Star` or `Cluster` run, and optionally restrict the ages saved in output tracks using `AgesOut`. -### Prerequisites -Make sure MORS and stellar evolution data are installed: +## Prerequisites + +Install MORS and download stellar evolution data: ```bash pip install fwl-mors mors download all ``` +!!! info "Units" + `Age` is in **Myr**, `Prot` is in **days**, and `Omega` is in units of the **current solar rotation rate** ($\Omega_\odot = 2.67 \times 10^{-6}$ rad s$^{-1}$). --- -### Step 1: Start from the default parameter set +## Step 1: Start from the default parameter set -Create a parameter dictionary identical to the internal defaults (`paramsDefault` in `parameters.py`): +Create a parameter dictionary identical to the internal defaults: ```python import mors -params = mors.NewParams() + +my_params = mors.NewParams() ``` -This gives you a normal Python dictionary you can edit. +This returns a standard Python dictionary that you can inspect and modify freely. --- -### Step 2: Change one or more parameters +## Step 2: Change one or more parameters + +**Option A: pass values directly to `NewParams` (recommended)** -**Option A: edit the dictionary after creation** ```python import mors -params = mors.NewParams() -params["param1"] = 1.5 -params["param2"] = 2.5 +my_params = mors.NewParams(Kwind=15.0, dAgeMax=10.0) ``` -**Option B: set values when calling `NewParams` (recommended)** +**Option B: edit the dictionary after creation** + ```python import mors -params = mors.NewParams(param1=1.5, param2=2.5) + +my_params = mors.NewParams() +my_params['Kwind'] = 15.0 +my_params['dAgeMax'] = 10.0 ``` +See the [Parameters](../Reference/parameters.md) page for the full list of available parameters and their defaults. + --- -### Step 3: Pass the parameter dictionary into `Star` (or `Cluster`) +## Step 3: Pass the parameter dictionary into `Star` or `Cluster` ```python import mors -star = mors.Star(Mstar=1.0, Omega=10.0, params=params) -``` -(Use the same `params=` keyword when creating a `mors.Cluster`.) +star = mors.Star(Mstar=1.0, Omega=10.0, params=my_params) +cluster = mors.Cluster(Mstar=Mstar, Omega=Omega, params=my_params) +``` --- -### Step 4: Discover available parameters +## Step 4: Print the current parameter values -To print a complete list of parameters (and their meanings/values as exposed by MORS): +`PrintParams` uses the logging system, so configure logging first: ```python import mors +from mors.logs import setup_logger + +setup_logger("INFO") + +# print all defaults mors.PrintParams() -``` -You can also inspect the source file `parameters.py` in your MORS installation. +# or print a custom dictionary +mors.PrintParams(params=my_params) +``` --- -### Step 5: Restrict output ages with `AgesOut` (optional) +## Step 5: Restrict output ages with `AgesOut` (optional) -By default, MORS computes tracks on its internal age grid. If you only need values at specific ages, set `AgesOut` so the saved tracks contain only those ages **plus the starting age (1 Myr)**. +By default, MORS saves tracks on its internal adaptive age grid. If you only need values at specific ages, pass `AgesOut` to restrict the saved output to those ages: + +**Single age:** -**Single age** ```python import mors + star = mors.Star(Mstar=1.0, Omega=10.0, AgesOut=100.0) # Myr ``` -**Multiple ages** +**Multiple ages:** + ```python import numpy as np import mors -ages = np.array([100.0, 200.0, 300.0, 400.0]) # Myr (ascending) +ages = np.array([100.0, 200.0, 300.0, 400.0]) # Myr, must be ascending star = mors.Star(Mstar=1.0, Omega=10.0, AgesOut=ages) ``` -Notes: -- The simulation ends at the **largest** age in `AgesOut`. -- Provide `AgesOut` in **ascending** order. -- If you later call interpolating helpers like `star.Lx(Age)` at arbitrary ages, results can be inaccurate if `AgesOut` is too sparse. -- Very large `AgesOut` grids can slow down calculations significantly. +!!! info "How `AgesOut` affects the simulation" + - The simulation runs from 1 Myr and ends at the **last age** in `AgesOut`. + - Ages below 1 Myr are silently dropped. + - `AgesOut` must be in **ascending** order. + - Interpolating functions like `star.Lx(Age=...)` can return inaccurate results if the saved grid is too sparse for the ages you query. --- -### Common pitfalls +## Common pitfalls + - `AgesOut` is in **Myr**. -- If you intend to query many arbitrary ages later, prefer the default age grid (don’t over-thin `AgesOut`). +- The simulation always starts at 1 Myr regardless of `AgesOut`. +- If you later need to query many arbitrary ages, prefer the default age grid rather than a sparse `AgesOut`. +- `PrintParams` produces no output unless logging is configured. \ No newline at end of file diff --git a/docs/How-to/track_quantities.md b/docs/How-to/track_quantities.md index 3e19e94..85049c4 100644 --- a/docs/How-to/track_quantities.md +++ b/docs/How-to/track_quantities.md @@ -1,73 +1,80 @@ -# Find stellar quantities using evolution tracks +# Find stellar quantities from evolution tracks -### Goal -Find basic stellar evolution properties (radius, luminosity, convective turnover time, moments of inertia, etc.) as a function of **stellar mass** and **age**, using the Spada et al. (2013) tracks bundled with MORS. Optionally, stellar evolution quantities according to the Baraffe model (Baraffe et al., 2002) can be found. +Find basic stellar evolution properties (radius, luminosity, convective turnover time, moments of inertia, etc.) as a function of stellar mass and age, using the Spada et al. (2013) [^spada] tracks bundled with MORS. Baraffe et al. (2015) [^baraffe] tracks are also available. -### Prerequisites -Install MORS and download the required data: +## Prerequisites + +Install MORS and download stellar evolution data: ```bash pip install fwl-mors mors download all ``` -!!! Units - `Mstar` in **M☉**, `Age` in **Myr**. Output units depend on the quantity (listed below). +!!! info "Units" + `Age` is in **Myr**, `Prot` is in **days**, and `Omega` is in units of the **current solar rotation rate** ($\Omega_\odot = 2.67 \times 10^{-6}$ rad s$^{-1}$). + +--- ## Spada tracks ### Available quantities -- `Rstar` — radius (**R☉**) -- `Lbol` — bolometric luminosity (**L☉**) -- `Teff` — effective temperature (**K**) -- `Itotal` — total moment of inertia (**g cm²**) -- `Icore` — core moment of inertia (**g cm²**) -- `Ienv` — envelope moment of inertia (**g cm²**) -- `Mcore` — core mass (**M☉**) -- `Menv` — envelope mass (**M☉**) -- `Rcore` — core radius (**R☉**) -- `tau` — convective turnover time (**days**) -- `dItotaldt` — d(Itotal)/dt (**g cm² Myr⁻¹**) -- `dIcoredt` — d(Icore)/dt (**g cm² Myr⁻¹**) -- `dIenvdt` — d(Ienv)/dt (**g cm² Myr⁻¹**) -- `dMcoredt` — d(Mcore)/dt (**M☉ Myr⁻¹**) -- `dRcoredt` — d(Rcore)/dt (**R☉ Myr⁻¹**) - -**Important definition note:** “core” and “envelope” here follow the rotation model definitions: -- core = everything interior to the outer convective zone -- envelope = the outer convective zone +| Function | Key for `Value` | Output units | +|---|---|---| +| `mors.Rstar` | `'Rstar'` | R☉ | +| `mors.Lbol` | `'Lbol'` | L☉ | +| `mors.Teff` | `'Teff'` | K | +| `mors.Itotal` | `'Itotal'` | g cm² | +| `mors.Icore` | `'Icore'` | g cm² | +| `mors.Ienv` | `'Ienv'` | g cm² | +| `mors.Mcore` | `'Mcore'` | M☉ | +| `mors.Menv` | `'Menv'` | M☉ | +| `mors.Rcore` | `'Rcore'` | R☉ | +| `mors.tauConv` | `'tauConv'` | days | +| `mors.dItotaldt` | `'dItotaldt'` | g cm² Myr⁻¹ | +| `mors.dIcoredt` | `'dIcoredt'` | g cm² Myr⁻¹ | +| `mors.dIenvdt` | `'dIenvdt'` | g cm² Myr⁻¹ | +| `mors.dMcoredt` | `'dMcoredt'` | M☉ Myr⁻¹ | +| `mors.dRcoredt` | `'dRcoredt'` | R☉ Myr⁻¹ | + +!!! info "Core and envelope definitions" + In MORS, "core" means everything interior to the outer convective zone, and "envelope" means the outer convective zone itself. This follows the rotation model convention and differs from the nuclear-burning core definition used in some other contexts. + --- -### Option 1: Call a property function directly +### Option 1: Call a quantity function directly -Each quantity has a dedicated function. For example, stellar radius: +Each quantity has a dedicated function: ```python import mors -Rstar = mors.Rstar(1.0, 1000.0) # Rsun -print(Rstar) + +Rstar = mors.Rstar(1.0, 1000.0) # Mstar=1.0 Msun, Age=1000 Myr +print(Rstar) # in Rsun ``` --- -### Option 2: Use the generic accessor (`Value`) +### Option 2: Use the generic `Value` accessor -If you want to choose the quantity by name at runtime: +Choose the quantity by name at runtime: ```python import mors -Rstar = mors.Value(1.0, 1000.0, "Rstar") + +Rstar = mors.Value(1.0, 1000.0, 'Rstar') print(Rstar) ``` --- -### Option 3: Vectorized inputs (arrays/lists) +### Option 3: Vectorised inputs + +All functions accept scalars, lists, or NumPy arrays for mass and age. -These functions accept lists or NumPy arrays for mass and/or age. +**Multiple masses, one age — returns a 1D array:** -**Multiple masses, one age → 1D array** ```python import numpy as np import mors @@ -77,7 +84,8 @@ R = mors.Rstar(masses, 1000.0) print(R.shape) # (3,) ``` -**One mass, multiple ages → 1D array** +**One mass, multiple ages — returns a 1D array:** + ```python import numpy as np import mors @@ -87,7 +95,8 @@ L = mors.Lbol(1.0, ages) print(L.shape) # (3,) ``` -**Multiple masses and multiple ages → 2D array** +**Multiple masses and multiple ages — returns a 2D array:** + ```python import numpy as np import mors @@ -95,69 +104,77 @@ import mors masses = np.array([0.8, 1.0, 1.2]) ages = np.array([10.0, 100.0, 1000.0]) T = mors.Teff(masses, ages) -print(T.shape) # (len(masses), len(ages)) +print(T.shape) # (3, 3) — shape (len(masses), len(ages)) ``` -**Multiple quantities via `Value`** +**Multiple quantities via `Value` — adds a third dimension:** + ```python import numpy as np import mors masses = np.array([0.9, 1.0]) ages = np.array([100.0, 1000.0]) -vals = mors.Value(masses, ages, ["Rstar", "Lbol", "tau"]) -# adds an extra dimension for the quantity list -print(vals.shape) +vals = mors.Value(masses, ages, ['Rstar', 'Lbol', 'tauConv']) +print(vals.shape) # (2, 2, 3) — (masses, ages, quantities) ``` --- -### Performance tip: pre-load a mass track (`LoadTrack`) +### Performance: pre-loading a mass track -The first time you call one of these functions, MORS loads and caches Spada tracks and writes a cache file (e.g., `SEmodels.pickle`) to speed up future runs. This cache can be deleted safely; it will be regenerated as needed. +On first use, MORS compiles the Spada grid and saves a cache file in the stellar evolution tracks directory. Subsequent runs load from this cache and are faster. -If you will query many ages for a particular mass, pre-load that mass track: +If you will query many ages for a specific mass, pre-load that track to avoid repeated interpolation between mass bins: ```python import mors -mors.LoadTrack(1.02) -``` -If it’s already loaded, this call does nothing. +mors.LoadTrack(1.02) # does nothing if already loaded +``` --- -## Baraffe tracks +## Baraffe tracks -MORS also provides access to Baraffe et al. (2002) tracks, which use **different units** than the Spada helpers above. +MORS also provides access to Baraffe et al. (2015) [^baraffe] tracks. -!!! Units - `Mstar` in **M☉** (valid range: ~0.01–1.4), `time` in **years (yr)**. +!!! info "Units" + `Mstar` in **M☉**, but time in **years (yr)** — not Myr. + +### Step 1: Load a Baraffe track -### Step 1. Load a Baraffe track (with interpolation) ```python import mors -Mstar = 0.5 -baraffe = mors.BaraffeTrack(Mstar) +baraffe = mors.BaraffeTrack(Mstar=0.5) ``` -`BaraffeTrack` performs mass interpolation (if needed) and interpolates time onto a fine grid. +`BaraffeTrack` performs mass interpolation if needed. + +### Step 2: Query radius, luminosity, and flux -### Step 2. Query radius, luminosity, and “solar constant” ```python time_yr = 1e7 # years -Rstar = baraffe.BaraffeStellarRadius(time_yr) # Rsun -Lbol = baraffe.BaraffeLuminosity(time_yr) # Lsun -Flux = baraffe.BaraffeSolarConstant(time_yr, 1.0) # W m^-2 at 1 AU +Rstar = baraffe.BaraffeStellarRadius(time_yr) # Rsun +Lbol = baraffe.BaraffeLuminosity(time_yr) # Lsun +Flux = baraffe.BaraffeSolarConstant(time_yr, 1.0) # W m⁻² at 1 AU ``` -`BaraffeSolarConstant(time, distance)` expects `distance` in **AU**. +`BaraffeSolarConstant(time, distance)` takes distance in **AU**. --- -### Common pitfalls -- Spada helpers use `Age` in **Myr**; Baraffe helpers use `time` in **yr**. -- “core” and “envelope” are defined by the rotation model (not the hydrogen-burning core definition). -- The first Spada call can be slower due to model loading + cache generation. +## Common pitfalls + +- Spada functions use `Age` in **Myr**; Baraffe functions use time in **yr**. +- The convective turnover time key is `'tauConv'`, not `'tau'`. +- "Core" and "envelope" follow the rotation model convention, not the nuclear-burning core definition. +- The first Spada call may be slower due to cache generation. + +--- + +[^spada]: Spada, F., Demarque, P., Kim, Y.-C., & Sills, A. (2013). The radius discrepancy in low-mass stars: single versus binaries. *The Astrophysical Journal, 776*(2), 87. https://doi.org/10.1088/0004-637X/776/2/87 + +[^baraffe]: Baraffe, I., Homeier, D., Allard, F., & Chabrier, G. (2015). New evolutionary models for pre-main sequence and main sequence stars down to the hydrogen-burning limit. *Astronomy & Astrophysics, 577*, A42. https://doi.org/10.1051/0004-6361/201425481 \ No newline at end of file diff --git a/docs/How-to/troubleshooting.md b/docs/How-to/troubleshooting.md new file mode 100644 index 0000000..31dba80 --- /dev/null +++ b/docs/How-to/troubleshooting.md @@ -0,0 +1,360 @@ +# Troubleshooting + +This page collects the most common errors and how to fix them. If you encounter errors that you cannot solve via the standard step-by-step guide or the advice below, [contact the developers](../Community/contact.md). + +## Error index + +| Error | Section | +|---|---| +| `FileNotFoundError` on `.track1` / `.track2` file | [Data and installation issues](#data-and-installation-issues) | +| Download fails or is incomplete | [Data and installation issues](#data-and-installation-issues) | +| `ValueError: Unrecognised folder name` | [Data and installation issues](#data-and-installation-issues) | +| `stellar mass cannot be less than lower limit of 0.1 Msun` | [Stellar mass and age errors](#stellar-mass-and-age-errors) | +| `stellar mass cannot be greater than upper limit of 1.25 Msun` | [Stellar mass and age errors](#stellar-mass-and-age-errors) | +| `input age X is not within limits of Y to Z` | [Stellar mass and age errors](#stellar-mass-and-age-errors) | +| `input stellar mass X is not within limits of Y to Z` | [Stellar mass and age errors](#stellar-mass-and-age-errors) | +| `must set either Omega or both OmegaEnv and OmegaCore as argument of Star` | [Rotation input errors](#rotation-input-errors) | +| `cannot set OmegaEnv and OmegaCore when Omega is set as argument of Star` | [Rotation input errors](#rotation-input-errors) | +| `cannot set both Omega and Prot as arguments of Star` | [Rotation input errors](#rotation-input-errors) | +| `cannot set both Age and OmegaCore as arguments of Star` | [Rotation input errors](#rotation-input-errors) | +| `cannot set both percentile and Omega` (or `OmegaEnv`, `OmegaCore`, `Prot`) | [Rotation input errors](#rotation-input-errors) | +| `if Age is set then must set either Omega or OmegaEnv as argument of Star` | [Rotation input errors](#rotation-input-errors) | +| `input rotation rate too low for given mass and age` | [Rotation fitting errors](#rotation-fitting-errors) | +| `input rotation rate too high for given mass and age` | [Rotation fitting errors](#rotation-fitting-errors) | +| `input rotation rate in valid range for mass and age but solver was unable to fit track` | [Rotation fitting errors](#rotation-fitting-errors) | +| `ending simulation due to bad data found` | [Numerical integration errors](#numerical-integration-errors) | +| `too many timesteps taken` | [Numerical integration errors](#numerical-integration-errors) | +| `invalid quantity` in `ActivityLifetime` | [Method and query errors](#method-and-query-errors) | +| `invalid Band` in `IntegrateEmission` | [Method and query errors](#method-and-query-errors) | +| `invalid aOrb` in `IntegrateEmission` | [Method and query errors](#method-and-query-errors) | +| `AgeMin not in range of evolutionary track` | [Method and query errors](#method-and-query-errors) | +| `Mstar and Omega have different lengths` | [Cluster-specific errors](#cluster-specific-errors) | +| Saved object has no callable quantity methods after loading | [Loading saved objects](#loading-saved-objects) | + + +If your issue is not listed here, please open an issue on [GitHub](https://github.com/FormingWorlds/MORS/issues) and include the full traceback and the inputs you used. You can also try to get more diagnostic information, as described [here](#getting-more-diagnostic-information). + + +--- + + + +## Data and installation issues + +### `FileNotFoundError` on first run + +**Symptom:** Python raises a `FileNotFoundError` when creating a `Star` or `Cluster` object, pointing to a `.track1` or `.track2` file inside the `stellar_evolution_tracks` directory. + +**Cause:** The stellar evolution data has not been downloaded yet, or was downloaded to a different location than where MORS is looking. + +**Fix:** Download the data: + +```sh +mors download all +``` + +Then check where MORS expects the data to be: + +```sh +mors env +``` + +If you have already downloaded the data to a custom location, point MORS to it with the `FWL_DATA` environment variable: + +```sh +export FWL_DATA=/path/to/your/data +``` + +Or pass the path directly when creating an object: + +```python +star = mors.Star(Mstar=1.0, Omega=1.0, starEvoDir="/path/to/tracks") +``` + +--- + +### Download fails or is incomplete + +**Symptom:** `mors download` exits with an error, or the data directory exists but is empty or missing files. + +**Cause:** MORS tries Zenodo first and then falls back to OSF, with a maximum of 2 attempts per source. A transient network issue or rate-limit can cause both to fail. + +**Fix:** Wait a few minutes and retry: + +```sh +mors download spada +mors download baraffe +``` + +You can also download each track set independently if one source succeeded and the other did not. If the Spada directory was partially created before the failure, remove it before retrying, otherwise MORS will assume it is complete and skip the download: + +```sh +rm -rf $FWL_DATA/stellar_evolution_tracks/Spada +mors download spada +``` + +!!! note "Incomplete directory" + After a successful Spada download, MORS automatically extracts the archive (`fs255_grid.tar.gz`) and removes it. If extraction failed mid-way, the directory may exist but be incomplete. Removing it and re-downloading is the safest fix. + +--- + +### `ValueError: Unrecognised folder name` + +**Symptom:** +``` +ValueError: Unrecognised folder name: +``` + +**Cause:** `DownloadEvolutionTracks` was called with an invalid folder name. Only `"Spada"` and `"Baraffe"` are accepted (case-sensitive). Passing an empty string downloads both. + +**Fix:** +```python +from mors.data import DownloadEvolutionTracks +DownloadEvolutionTracks("Spada") # or "Baraffe", or "" for both +``` + +--- + +## Stellar mass and age errors + +### Mass outside of supported range +- `stellar mass cannot be less than lower limit of 0.1 Msun` +- `stellar mass cannot be greater than upper limit of 1.25 Msun` + +**Cause:** MORS only supports stellar masses in the range **0.1 – 1.25 Msun**, set by `MstarMin = 0.1` and `MstarMax = 1.25` in `star.py`. Passing a value outside this range raises an exception immediately. + +**Fix:** Ensure your stellar mass is within the supported range before creating a `Star`: + +```python +# Valid +star = mors.Star(Mstar=1.0, Omega=1.0) + +# Invalid — will raise an exception +star = mors.Star(Mstar=1.5, Omega=1.0) +``` + +--- + +### `input age X is not within limits of Y to Z` + +**Cause:** The requested age falls outside the age range covered by the stellar evolution track for this mass. The track starts at 1 Myr and ends at the end of the main sequence (varies with mass). + +**Fix:** Check the age range of the loaded track before querying it: + +```python +print(star.AgeTrack[0], star.AgeTrack[-1]) # start and end ages in Myr +``` + +Only query ages within this range when calling `star.Value()` or any of the quantity accessor functions. + +--- + +### `input stellar mass X is not within limits of Y to Z` + +**Cause:** Raised during 2D interpolation in `stellarevo.py` when the requested mass falls outside the range of masses currently loaded in `ModelData`. This can happen if `ClearData=True` was used when calling `StarEvo.LoadTrack` and a different mass is subsequently requested. + +--- + +## Rotation input errors + +### `must set either Omega or both OmegaEnv and OmegaCore as argument of Star` + +**Cause:** No rotation rate was provided. `Star` requires either `Omega` (sets both envelope and core to the same value), or both `OmegaEnv` and `OmegaCore` explicitly. + +**Fix:** +```python +# Option 1: single rotation rate (envelope = core) +star = mors.Star(Mstar=1.0, Omega=1.0) + +# Option 2: separate envelope and core rates +star = mors.Star(Mstar=1.0, OmegaEnv=2.0, OmegaCore=1.5) +``` + +--- + +### `cannot set OmegaEnv and OmegaCore when Omega is set as argument of Star` + +**Cause:** `Omega` was set alongside `OmegaEnv` or `OmegaCore`. These are mutually exclusive: `Omega` is a shorthand that sets both envelope and core to the same value. + +--- + +### `cannot set both Omega and Prot as arguments of Star` + +**Cause:** Both `Omega` (rotation rate in units of Ωsun) and `Prot` (rotation period in days) were provided. Only one may be specified. + +--- + +### `cannot set both Age and OmegaCore as arguments of Star` + +**Cause:** When `Age` is provided, MORS fits the evolutionary track to pass through the given surface rotation at that age. In this mode, the core rotation at the initial time is determined by the fit and cannot be set independently. + +--- + +### `cannot set both percentile and Omega` (or `OmegaEnv`, `OmegaCore`, `Prot`) + +**Cause:** `percentile` specifies the rotation rate via the empirical 1 Myr rotation distribution and is mutually exclusive with all direct rotation rate inputs. + +**Fix:** Use one or the other: + +```python +# Specify rotation directly +star = mors.Star(Mstar=1.0, Omega=5.0) + +# Or use a percentile of the rotation distribution +star = mors.Star(Mstar=1.0, percentile=50.0) + +# String shortcuts are also accepted +star = mors.Star(Mstar=1.0, percentile='slow') # 5th percentile +star = mors.Star(Mstar=1.0, percentile='medium') # 50th percentile +star = mors.Star(Mstar=1.0, percentile='fast') # 95th percentile +``` + +--- + +### `if Age is set then must set either Omega or OmegaEnv as argument of Star` + +**Cause:** `Age` was provided (to fit the track to a known rotation at a known age) but no surface rotation rate was given. When `Age` is set, either `Omega` or `OmegaEnv` must also be set; `OmegaCore` cannot be used in this mode. + +--- + +## Rotation fitting errors + +These errors are raised by `rotevo.FitRotation` when MORS tries to find the initial rotation rate that produces a given rotation at a given age. + +### `input rotation rate too low for given mass and age` + +**Cause:** The requested rotation rate at the given age is lower than what the slowest possible initial rotator (Ω₀ = 0.1 Ωsun) would produce. The star would need to have started rotating slower than any physically supported initial rate. + +--- + +### `input rotation rate too high for given mass and age` + +**Cause:** The requested rotation rate at the given age is higher than what the fastest possible initial rotator (Ω₀ = 50 Ωsun) would produce. + +--- + +### `input rotation rate in valid range for mass and age but solver was unable to fit track` + +**Cause:** The requested rotation is within the fittable range but the bisection solver failed to converge within `params['nStepMaxFit']` = 1000 steps to within the tolerance `params['toleranceFit']` = 1 × 10⁻⁵. This is rare in normal use. + +**Fix:** If you hit this, try relaxing the tolerance or increasing the step limit via a custom parameter dictionary: + +```python +import mors +import mors.parameters as params + +my_params = params.NewParams(toleranceFit=1e-4, nStepMaxFit=5000) +star = mors.Star(Mstar=1.0, Age=100.0, Omega=10.0, params=my_params) +``` + +--- + +## Numerical integration errors + +### `ending simulation due to bad data found` + +**Cause:** The ODE solver produced a NaN, Inf, or non-positive rotation rate (OmegaEnv ≤ 0 or OmegaCore ≤ 0). This indicates the simulation has become numerically unstable. Possible causes include an extreme initial rotation rate or unusual parameter combinations. + +**Fix:** Check your inputs. If using custom parameters, try reducing `dAgeMax` (the maximum allowed timestep) to improve stability: + +```python +my_params = params.NewParams(dAgeMax=10.0) # default is 50 Myr +``` + +--- + +### `too many timesteps taken` + +**Cause:** The solver exceeded `params['nStepMax']` = 1,000,000 timesteps without reaching the end age. This should not occur with the default `RosenbrockFixed` solver under normal conditions. + +--- + +## Method and query errors + +### `invalid quantity` in `ActivityLifetime` + +**Cause:** The `Quantity` argument is not in the list of supported quantities. The full list of valid values is: + +`'Lx'`, `'Fx'`, `'Rx'`, `'FxHZ'`, `'Leuv1'`, `'Feuv1'`, `'Reuv1'`, `'Feuv1HZ'`, `'Leuv2'`, `'Feuv2'`, `'Reuv2'`, `'Feuv2HZ'`, `'Leuv'`, `'Feuv'`, `'Reuv'`, `'FeuvHZ'`, `'Lly'`, `'Fly'`, `'Rly'`, `'FlyHZ'` + +Note that `Threshold='sat'` is a valid special value, which normalises the track to the saturation rotation rate and sets the threshold to 1. + +--- + +### `invalid Band` in `IntegrateEmission` + +**Cause:** The `Band` argument is not one of the supported wavelength bands. Valid values are: + +`'XUV'`, `'Xray'`, `'EUV1'`, `'EUV2'`, `'EUV'`, `'Lyman'`, `'bol'` + +--- + +### `invalid aOrb` in `IntegrateEmission` + +**Cause:** A string was passed for `aOrb` that is not one of the recognised habitable zone boundaries. Valid string values are: + +`'RecentVenus'`, `'RunawayGreenhouse'`, `'MoistGreenhouse'`, `'MaximumGreenhouse'`, `'EarlyMars'`, `'HZ'` + +You can also pass a numerical value in AU directly. + +--- + +### `AgeMin not in range of evolutionary track` / `AgeMax not in range of evolutionary track` + +**Cause:** Raised by `IntegrateEmission` when either `AgeMin` or `AgeMax` falls outside the age range of the star's evolutionary track. Both must satisfy `Age[0] <= AgeMin <= Age[-1]` and `Age[0] <= AgeMax <= Age[-1]`. + +--- + +## Cluster-specific errors + +### `Mstar and Omega have different lengths` + +**Cause:** When creating a `Cluster`, the `Mstar` and `Omega` (or `OmegaEnv` / `OmegaCore`) arrays must all have the same length. MORS checks each pair independently and raises a specific message for whichever mismatches. + +**Fix:** +```python +import numpy as np +import mors + +Mstar = np.array([0.5, 0.8, 1.0]) +Omega = np.array([1.0, 2.0, 5.0]) # must be same length as Mstar + +cluster = mors.Cluster(Mstar=Mstar, Omega=Omega) +``` + +--- + +## Loading saved objects + +### Saved `Star` or `Cluster` has no callable quantity methods after loading + +**Cause:** `pickle` does not restore the dynamically attached methods (e.g. `star.Lx(Age=...)`) that `Star` and `Cluster` set up at creation time. If you load a saved object with `pickle.load()` directly, these methods will be missing. + +**Fix:** Always use `mors.miscellaneous.Load` instead of `pickle.load`: + +```python +import mors.miscellaneous as misc + +star = misc.Load("star.pickle") # correct: re-attaches quantity functions +cluster = misc.Load("cluster.pickle") # correct +``` + +--- + +## Getting more diagnostic information + +MORS uses Python's standard `logging` module under the logger name `fwl.*`. To enable debug output, configure the logger before running your script: + +```python +import logging +logging.basicConfig(level=logging.DEBUG) +``` + +Or use the convenience function provided in `mors.logs`: + +```python +from mors.logs import setup_logger +log = setup_logger("DEBUG") +``` + +This will print detailed per-step information from the ODE solver and data loading routines, which can help identify where a problem originates. diff --git a/docs/Reference/api/baraffe.md b/docs/Reference/api/baraffe.md new file mode 100644 index 0000000..9b22eb2 --- /dev/null +++ b/docs/Reference/api/baraffe.md @@ -0,0 +1,7 @@ +# mors.baraffe + +::: mors.baraffe + options: + members: true + inherited_members: false + show_source: true \ No newline at end of file diff --git a/docs/Reference/api/cli.md b/docs/Reference/api/cli.md new file mode 100644 index 0000000..9275635 --- /dev/null +++ b/docs/Reference/api/cli.md @@ -0,0 +1,7 @@ +# mors.cli + +::: mors.cli + options: + members: true + inherited_members: false + show_source: true \ No newline at end of file diff --git a/docs/Reference/api/cluster.md b/docs/Reference/api/cluster.md new file mode 100644 index 0000000..53b76dd --- /dev/null +++ b/docs/Reference/api/cluster.md @@ -0,0 +1,7 @@ +# mors.cluster + +::: mors.cluster + options: + members: true + inherited_members: false + show_source: true \ No newline at end of file diff --git a/docs/Reference/api/constants.md b/docs/Reference/api/constants.md new file mode 100644 index 0000000..08d2d95 --- /dev/null +++ b/docs/Reference/api/constants.md @@ -0,0 +1,74 @@ +# mors.constants + +::: mors.constants + options: + members: false + inherited_members: false + show_source: true + +All constants are defined in `constants.py` in CGS units unless otherwise noted. + +## Time + +| Name | Value | Units | +|---|---|---| +| `sec` | $1.0$ | s | +| `minute` | $60.0$ | s | +| `hour` | $3600.0$ | s | +| `day` | $86400.0$ | s | +| `year` | $3.1558 \times 10^{7}$ | s | +| `Myr` | $3.1558 \times 10^{13}$ | s | +| `Gyr` | $3.1558 \times 10^{16}$ | s | + +## Distance (CGS) + +| Name | Value | Units | +|---|---|---| +| `A` | $1.0 \times 10^{-8}$ | cm (Ångström) | +| `nm` | $1.0 \times 10^{-7}$ | cm | +| `micron` | $1.0 \times 10^{-4}$ | cm | +| `mm` | $0.1$ | cm | +| `cm` | $1.0$ | cm | +| `m` | $1.0 \times 10^{3}$ | cm | +| `km` | $1.0 \times 10^{5}$ | cm | +| `Rearth` | $6.371 \times 10^{8}$ | cm | +| `Rjup` | $6.9911 \times 10^{9}$ | cm | +| `Rsun` | $6.957 \times 10^{10}$ | cm | +| `AU` | $1.496 \times 10^{13}$ | cm | +| `AU_SI` | $1.495979 \times 10^{11}$ | m | + +## Mass (CGS) + +| Name | Value | Units | +|---|---|---| +| `g` | $1.0$ | g | +| `kg` | $1.0 \times 10^{3}$ | g | +| `Mearth` | $5.972 \times 10^{27}$ | g | +| `Mjup` | $1.89813 \times 10^{30}$ | g | +| `Msun` | $1.99 \times 10^{33}$ | g | +| `Mproton` | $1.6726219 \times 10^{-24}$ | g | +| `Melec` | $9.10938356 \times 10^{-28}$ | g | +| `Msunyr_` | $\dot{M}_\odot\,\mathrm{yr}^{-1}$ | $\mathrm{g\,s}^{-1}$ | + +## Solar quantities + +| Name | Value | Units | Description | +|---|---|---|---| +| `LbolSun` | $3.828 \times 10^{33}$ | $\mathrm{erg\,s}^{-1}$ | Solar bolometric luminosity | +| `LbolSun_SI` | $3.828 \times 10^{26}$ | W | Solar bolometric luminosity (SI) | +| `AgeSun` | $4567.0$ | Myr | Solar age | +| `OmegaSun` | $2.67 \times 10^{-6}$ | $\mathrm{rad\,s}^{-1}$ | Solar rotation rate | +| `ProtSun` | $2\pi / \Omega_\odot$ | days | Solar rotation period | +| `tauConvSun` | $28.436$ | days | Solar convective turnover time (Spada et al. 2013) | +| `RoSun` | $P_{\mathrm{rot},\odot} / \tau_\odot$ | — | Solar Rossby number | + +## Physical constants + +| Name | Value | Units | Description | +|---|---|---|---| +| `GravConstant` | $6.674 \times 10^{-8}$ | $\mathrm{cm}^{3}\,\mathrm{g}^{-1}\,\mathrm{s}^{-2}$ | Gravitational constant | +| `kB` | $1.38064852 \times 10^{-16}$ | $\mathrm{erg\,K}^{-1}$ | Boltzmann constant | +| `Pi` | $3.14159265359$ | — | $\pi$ | +| `h_SI` | $6.626075540 \times 10^{-34}$ | $\mathrm{J\,s}$ | Planck constant | +| `c_SI` | $2.99792458 \times 10^{8}$ | $\mathrm{m\,s}^{-1}$ | Speed of light | +| `k_SI` | $1.38065812 \times 10^{-23}$ | $\mathrm{J\,K}^{-1}$ | Boltzmann constant (SI) | \ No newline at end of file diff --git a/docs/Reference/api/data.md b/docs/Reference/api/data.md new file mode 100644 index 0000000..094d52d --- /dev/null +++ b/docs/Reference/api/data.md @@ -0,0 +1,7 @@ +# mors.data + +::: mors.data + options: + members: true + inherited_members: false + show_source: true \ No newline at end of file diff --git a/docs/Reference/api/index.md b/docs/Reference/api/index.md new file mode 100644 index 0000000..3fbcac7 --- /dev/null +++ b/docs/Reference/api/index.md @@ -0,0 +1,49 @@ +# API overview + +This is a detailed overview of MORS' API for the user's reference. If you want to understand the underlying model, please visit the [model overview](../../Explanations/structure.md). + +| Module | Description | +|---|---| +| **User-facing classes** | | +| [`mors.star`](star.md) | `Star` class: full rotation and XUV evolutionary tracks for a single star | +| [`mors.cluster`](cluster.md) | `Cluster` class: vectorised `Star` evolution for an array of stars | +| **Physical model** | | +| [`mors.physicalmodel`](physicalmodel.md) | Torque equations, X-ray/EUV/Ly-α emission, HZ boundaries, ODE right-hand side | +| [`mors.rotevo`](rotevo.md) | ODE solvers (RK4, RKF, Rosenbrock) and `FitRotation` bisection | +| **Stellar structure** | | +| [`mors.stellarevo`](stellarevo.md) | Spada et al. (2013) grid loading, interpolation, and `StarEvo` class | +| [`mors.baraffe`](baraffe.md) | Baraffe et al. (2015) track loading and interpolation | +| **Spectral synthesis** | | +| [`mors.spectrum`](spectrum.md) | `Spectrum` class: continuous stellar spectra and band integration | +| [`mors.synthesis`](synthesis.md) | Historical spectrum scaling from a modern reference spectrum | +| **Utilities** | | +| [`mors.miscellaneous`](miscellaneous.md) | `Load`, `ModelCluster`, `ActivityLifetime`, `IntegrateEmission` | +| [`mors.parameters`](parameters.md) | Default parameter dictionary, `NewParams`, `PrintParams` | +| [`mors.constants`](constants.md) | Physical constants and solar reference values (CGS) | +| [`mors.data`](data.md) | Stellar evolution track downloads (Zenodo + OSF) | +| [`mors.cli`](cli.md) | Command-line interface (`mors download`, `mors env`) | + +--- + +## Source tree + +``` +src/mors +├── __init__.py # Public API, re-exports all user-facing functions and classes +├── constants.py # Physical constants and solar reference values (CGS units) +├── parameters.py # Default model parameter dictionary and NewParams/PrintParams helpers +├── data.py # Stellar evolution track downloads (Zenodo + OSF) +├── data/ +│ └── ModelDistribution.dat # Built-in 1 Myr rotation distribution (masses + Omega) +├── stellarevo.py # Spada et al. (2013) grid loading, interpolation, and StarEvo class +├── baraffe.py # Baraffe et al. (2015) track loading and interpolation +├── physicalmodel.py # Torques, X-ray/EUV/Ly-α emission, HZ boundaries, ODE right-hand side +├── rotevo.py # ODE solvers (RK4, RKF, Rosenbrock) and FitRotation bisection +├── star.py # Star class: full rotation + XUV evolutionary tracks for one star +├── cluster.py # Cluster class: vectorised Star evolution for an array of stars +├── spectrum.py # Spectrum class: continuous stellar spectra and band integration +├── synthesis.py # Historical spectrum scaling from a modern reference spectrum +├── miscellaneous.py # Load, ModelCluster, ActivityLifetime, IntegrateEmission utilities +├── logs.py # Logging setup (setup_logger) +└── cli.py # Command-line interface (mors download, mors env) +``` \ No newline at end of file diff --git a/docs/Reference/api/miscellaneous.md b/docs/Reference/api/miscellaneous.md new file mode 100644 index 0000000..9899b1f --- /dev/null +++ b/docs/Reference/api/miscellaneous.md @@ -0,0 +1,7 @@ +# mors.miscellaneous + +::: mors.miscellaneous + options: + members: true + inherited_members: false + show_source: true \ No newline at end of file diff --git a/docs/Reference/api/parameters.md b/docs/Reference/api/parameters.md new file mode 100644 index 0000000..3df9a06 --- /dev/null +++ b/docs/Reference/api/parameters.md @@ -0,0 +1,9 @@ +# mors.parameters + +A full list of model parameters defined in this module can be found in the [parameter reference](../parameters.md). + +::: mors.parameters + options: + members: true + inherited_members: false + show_source: true \ No newline at end of file diff --git a/docs/Reference/api/physicalmodel.md b/docs/Reference/api/physicalmodel.md new file mode 100644 index 0000000..8451659 --- /dev/null +++ b/docs/Reference/api/physicalmodel.md @@ -0,0 +1,7 @@ +# mors.physicalmodel + +::: mors.physicalmodel + options: + members: true + inherited_members: false + show_source: true \ No newline at end of file diff --git a/docs/Reference/api/rotevo.md b/docs/Reference/api/rotevo.md new file mode 100644 index 0000000..544d9d3 --- /dev/null +++ b/docs/Reference/api/rotevo.md @@ -0,0 +1,7 @@ +# mors.rotevo + +::: mors.rotevo + options: + members: true + inherited_members: false + show_source: true \ No newline at end of file diff --git a/docs/Reference/api/spectrum.md b/docs/Reference/api/spectrum.md new file mode 100644 index 0000000..5ca2030 --- /dev/null +++ b/docs/Reference/api/spectrum.md @@ -0,0 +1,7 @@ +# mors.spectrum + +::: mors.spectrum + options: + members: true + inherited_members: false + show_source: true \ No newline at end of file diff --git a/docs/Reference/api/star.md b/docs/Reference/api/star.md new file mode 100644 index 0000000..ac4065a --- /dev/null +++ b/docs/Reference/api/star.md @@ -0,0 +1,7 @@ +# mors.star + +::: mors.star + options: + members: true + inherited_members: false + show_source: true \ No newline at end of file diff --git a/docs/Reference/api/stellarevo.md b/docs/Reference/api/stellarevo.md new file mode 100644 index 0000000..9286c27 --- /dev/null +++ b/docs/Reference/api/stellarevo.md @@ -0,0 +1,7 @@ +# mors.stellarevo + +::: mors.stellarevo + options: + members: true + inherited_members: false + show_source: true \ No newline at end of file diff --git a/docs/Reference/api/synthesis.md b/docs/Reference/api/synthesis.md new file mode 100644 index 0000000..8eefcf4 --- /dev/null +++ b/docs/Reference/api/synthesis.md @@ -0,0 +1,7 @@ +# mors.synthesis + +::: mors.synthesis + options: + members: true + inherited_members: false + show_source: true \ No newline at end of file diff --git a/docs/Reference/parameters.md b/docs/Reference/parameters.md new file mode 100644 index 0000000..db145a3 --- /dev/null +++ b/docs/Reference/parameters.md @@ -0,0 +1,99 @@ +# Model parameter reference + +All model behaviour is controlled through a single dictionary (`parameters.paramsDefault`). A modified parameter dictionary can be created with `parameters.NewParams(**kwargs)`, which copies the defaults and overrides the specified keys: + +```python +import mors +my_params = mors.NewParams(dAgeMax=10.0, CoreEnvelopeDecoupling=False) +star = mors.Star(Mstar=1.0, Omega=1.0, params=my_params) +``` + +## Numerical integration + +| Parameter | Default | Description | +|---|---|---| +| `TimeIntegrationMethod` | `'RosenbrockFixed'` | ODE solver (options: `ForwardEuler`, `RungeKutta4`, `RungeKuttaFehlberg`, `Rosenbrock`, `RosenbrockFixed`) | +| `AgeMinDefault` | $1.0$ Myr | Default start age of evolutionary tracks | +| `AgeMaxDefault` | $5000.0$ Myr | Default end age of evolutionary tracks | +| `dAgeMin` | $10^{-5}$ Myr | Minimum allowed timestep | +| `dAgeMax` | $50.0$ Myr | Maximum allowed timestep | +| `nStepMax` | $10^6$ | Maximum number of timesteps before error | + +## Physical processes + +| Parameter | Default | Description | +|---|---|---| +| `CoreEnvelopeDecoupling` | `True` | Enable two-zone core–envelope model | +| `WindTorque` | `True` | Include wind spin-down torque | +| `CoreEnvelopeTorque` | `True` | Include core–envelope coupling torque | +| `DiskLocking` | `True` | Include disk-locking torque | +| `MomentInertiaChangeTorque` | `True` | Include moment of inertia change torque (do not disable) | +| `CoreGrowthTorque` | `True` | Include core-growth torque (do not disable) | +| `BreakupMdotIncrease` | `True` | Enhance $\dot{M}$ when approaching breakup rotation | +| `ExtendedTracks` | `False` | Return full set of output quantities (overridden to `True` by `Star` and `Cluster`) | + +## Two-zone thresholds + +| Parameter | Default | Description | +|---|---|---| +| `MstarThresholdCE` | $0.35\,M_\odot$ | Stars below this mass are treated as fully convective | +| `IcoreThresholdCE` | $0.01$ | Core–envelope decoupling disabled if $I_\mathrm{core}/I_\mathrm{total}$ is below this value | + +## Disk locking + +| Parameter | Default | Description | +|---|---|---| +| `aDiskLock` | $13.5$ | Coefficient $a$ in $t_\mathrm{disk} = a\,\Omega_0^b$ | +| `bDiskLock` | $-0.5$ | Exponent $b$ in $t_\mathrm{disk} = a\,\Omega_0^b$ | +| `ageDLmax` | $15.0$ Myr | Maximum disk-locking age | + +## Wind and magnetic field + +| Parameter | Default | Description | +|---|---|---| +| `Kwind` | $11.0$ | Wind torque normalisation constant $K_\tau$ | +| `BdipSun` | $1.35$ G | Solar dipole field strength $B_{\mathrm{dip},\odot}$ | +| `aBdip` | $-1.32$ | Power-law index of $B_\mathrm{dip}$–$Ro$ relation | +| `RoSatBdip` | $0.0605$ | Saturation Rossby number for $B_\mathrm{dip}$ | +| `MdotSun` | $1.4 \times 10^{-14}\,M_\odot\,\mathrm{yr}^{-1}$ | Solar mass loss rate | +| `aMdot` | $-1.7591$ | Power-law index on $Ro$ in $\dot{M}$ relation | +| `bMdot` | $0.6494$ | Power-law index on $M_\star$ in $\dot{M}$ relation | +| `RoSatMdot` | $0.0605$ | Saturation Rossby number for $\dot{M}$ | +| `fracBreakThreshold` | $0.1$ | Fraction of $\Omega_\mathrm{break}$ at which magnetocentrifugal enhancement begins | + +## Core–envelope coupling + +| Parameter | Default | Description | +|---|---|---| +| `aCoreEnvelope` | $25.6015$ | Coefficient $a_\mathrm{ce}$ in coupling timescale | +| `bCoreEnvelope` | $-3.4817 \times 10^{-2}$ | Exponent $b_\mathrm{ce}$ in coupling timescale | +| `cCoreEnvelope` | $-0.4476$ | Exponent $c_\mathrm{ce}$ in coupling timescale | +| `timeCEmin` | $0.0$ Myr | Minimum allowed core–envelope coupling timescale | + +## X-ray emission + +| Parameter | Default | Description | +|---|---|---| +| `RoSatXray` | $0.0605$ | Saturation Rossby number for X-ray emission | +| `RxSatXray` | $5.135 \times 10^{-4}$ | $R_X$ at the saturation threshold | +| `beta1Xray` | $-0.135$ | Power-law index $\beta_1$ in the saturated regime | +| `beta2Xray` | $-1.889$ | Power-law index $\beta_2$ in the unsaturated regime | +| `sigmaXray` | $0.359$ dex | Standard deviation of log-normal X-ray variability scatter | + +## Rotation fitting + +| Parameter | Default | Description | +|---|---|---| +| `Omega0FitMin` | $0.1\,\Omega_\odot$ | Minimum initial rotation rate considered when fitting | +| `Omega0FitMax` | $50.0\,\Omega_\odot$ | Maximum initial rotation rate considered when fitting | +| `nStepMaxFit` | $1000$ | Maximum bisection steps when fitting initial rotation | +| `toleranceFit` | $10^{-5}$ | Convergence tolerance for rotation fitting | + +## Other + +| Parameter | Default | Description | +|---|---|---| +| `dMstarPer` | $0.1\,M_\odot$ | Half-width of mass bin used when computing rotation percentiles | +| `AgeHZ` | $5000.0$ Myr | Age at which habitable zone boundaries are calculated | + +A modified parameter dictionary can be created with `parameters.NewParams(**kwargs)`, which copies the defaults and overrides the specified keys. \ No newline at end of file diff --git a/docs/Tutorials/first_run.md b/docs/Tutorials/first_run.md index a8fbf45..657e6bd 100644 --- a/docs/Tutorials/first_run.md +++ b/docs/Tutorials/first_run.md @@ -1,33 +1,23 @@ -# Tutorial: First run +# Tutorial: First run -## What you’ll do -By the end of this tutorial you will: +By the end of this tutorial you will have installed MORS, computed your first stellar evolutionary track, plotted a quantity, queried values at specific ages, and saved the result for reuse. -1. Install MORS and download the required data -2. Create a `Star` and compute evolutionary tracks -3. Inspect available tracks + units -4. Plot one track -5. Find stellar values at specific ages -6. Save and reload the result +!!! info "Units" + `Age` is in **Myr**, `Prot` is in **days**, and `Omega` is in units of the **current solar rotation rate** ($\Omega_\odot = 2.67 \times 10^{-6}$ rad s$^{-1}$). -!!! Units - `Mstar` in **M☉**, `Age` in **Myr**, `Prot` in **days**, `Omega` in **Ω☉**. +## Prerequisites ---- - -## 0. Prerequisites -You need: +You need a Python 3 environment with `pip` and a working internet connection for the one-time data download. -- Python 3 environment with `pip` -- A working internet connection (for downloading data once) +Optionally, create and activate an isolated environment first. With Conda: -**Optional**: Create and activate a Conda environment (requires `conda` installed): ```bash conda create -n mors python=3.11 -y conda activate mors ``` -No `conda`? create and activate a virtual environment (venv): +Or with a standard virtual environment: + ```bash python -m venv .venv source .venv/bin/activate @@ -35,119 +25,150 @@ source .venv/bin/activate --- -## 1. Install MORS +## Step 1: Install MORS + ```bash pip install fwl-mors ``` -Quick sanity check: +Verify the installation: + ```bash -python -c "import mors; print('mors imported:', mors.__version__ if hasattr(mors,'__version__') else 'ok')" +python -c "import mors; print('ok')" ``` --- -## 2. Download stellar evolution data -MORS requires stellar evolution tracks (downloaded once): +## Step 2: Download stellar evolution data + +MORS requires stellar evolution tracks, downloaded once: + ```bash mors download all ``` -See where data are stored: +To check where the data are stored: + ```bash mors env ``` -If you want to place data somewhere else, set `FWL_DATA` (optional): +If you want to store data somewhere specific, set the `FWL_DATA` environment variable before downloading: + ```bash export FWL_DATA=/path/to/your/data +mors download all ``` --- -## 3. Create your first star -Create a 1 M☉ star with an initial rotation period of 2.7 days (at ~1 Myr): +## Step 3: Create your first star + +Create a 1 Msun star with an initial rotation period of 2.7 days. When `Age` is not specified, MORS treats the rotation as the initial value at `AgeMinDefault = 1 Myr` and evolves the track forward from there: + ```python import mors + star = mors.Star(Mstar=1.0, Prot=2.7) ``` -Alternative: specify initial rotation as Ω/Ω☉: +You can also specify the initial rotation rate as a multiple of the solar rotation rate: + ```python star = mors.Star(Mstar=1.0, Omega=10.0) ``` --- -## 4. Inspect what tracks exist -Print track names and units: +## Step 4: Inspect available tracks + +Print all available track names and their units: + ```python star.PrintAvailableTracks() -print("Lx units:", star.Units.get("Lx")) +print(star.Units['Lx']) ``` -You can access arrays either via the `Tracks` dict: -```python -age = star.Tracks["Age"] -lx = star.Tracks["Lx"] -``` -or via convenience attributes: +!!! note + `PrintAvailableTracks` uses the logging system. If you see no output, configure logging first: + ```python + from mors.logs import setup_logger + setup_logger("INFO") + star.PrintAvailableTracks() + ``` + +Track arrays are accessible via the `Tracks` dictionary or as `Track` attributes directly on the star: + ```python -age = star.AgeTrack -lx = star.LxTrack +age = star.Tracks['Age'] # or: star.AgeTrack +lx = star.Tracks['Lx'] # or: star.LxTrack ``` --- -## 5. Plot a track +## Step 5: Plot a track + ```python import matplotlib.pyplot as plt -plt.plot(star.AgeTrack, star.LxTrack) -plt.xlabel(f"Age [{star.Units['Age']}]") -plt.ylabel(f"Lx [{star.Units['Lx']}]") +fig, ax = plt.subplots() +ax.plot(star.AgeTrack, star.LxTrack) +ax.set_xlabel(f"Age [{star.Units['Age']}]") +ax.set_ylabel(f"Lx [{star.Units['Lx']}]") +ax.set_xscale('log') +ax.set_yscale('log') plt.show() ``` -If you see a plot and no errors, your installation + data are working. +If you see a plot with no errors, your installation and data download are working correctly. --- -## 6. Find stellar values at specific ages -Use the generic accessor: +## Step 6: Query values at specific ages + +Use the generic `Value` method: + ```python -print(star.Value(Age=150.0, Quantity="Lx")) +lx = star.Value(Age=150.0, Quantity='Lx') +print(lx) ``` -Or a convenience method (when available): +Or the quantity accessor directly: + ```python -print(star.Lx(150.0)) +lx = star.Lx(150.0) +print(lx) ``` +Both return a linearly interpolated scalar at the requested age. + --- -## 7. (Optional) Try percentiles: slow/medium/fast rotators -This uses the built-in model distribution: +## Step 7: Compare slow, medium, and fast rotators (optional) + +MORS includes a built-in 1 Myr rotation distribution. You can create stars at the 5th, 50th, and 95th percentiles using string shortcuts: + ```python -slow = mors.Star(Mstar=1.0, percentile="slow") # 5th percentile -medium = mors.Star(Mstar=1.0, percentile="medium") # 50th percentile -fast = mors.Star(Mstar=1.0, percentile="fast") # 95th percentile +slow = mors.Star(Mstar=1.0, percentile='slow') # 5th percentile +medium = mors.Star(Mstar=1.0, percentile='medium') # 50th percentile +fast = mors.Star(Mstar=1.0, percentile='fast') # 95th percentile -print("slow percentile:", slow.percentile) -print("fast percentile:", fast.percentile) +print(slow.percentile, medium.percentile, fast.percentile) ``` --- -## 8. Save and reload (recommended) -Save: +## Step 8: Save and reload + +Saving avoids recomputing the tracks next time: + ```python -star.Save(filename="star.pickle") +star.Save(filename='star.pickle') ``` -Reload later: +Reload with `mors.Load` rather than `pickle.load` directly, as it re-attaches the quantity accessor functions: + ```python -import mors -star2 = mors.Load("star.pickle") +star2 = mors.Load('star.pickle') print(star2.Lx(150.0)) -``` +``` \ No newline at end of file diff --git a/docs/getting_started.md b/docs/getting_started.md new file mode 100644 index 0000000..d42c1a1 --- /dev/null +++ b/docs/getting_started.md @@ -0,0 +1,100 @@ +# Getting started + +!!! note "Usage within the PROTEUS framework" + MORS is most commonly installed and used as integrated into the PROTEUS framework. Understand how to use PROTEUS [here](https://proteus-framework.org/PROTEUS). + +## Quick path + +Here is the quickest path to getting started: + +1. **Install MORS** + Follow the installation steps and download necessary data.
+ → [Installation guide](How-to/installation.md) + +2. **Run your first workflow** + Learn the basic usage patterns and common configurations.
+ → [Using MORS](Tutorials/first_run.md) + +--- + +## What do you want to do? + +### Get started + +
+ +- :material-download: **Install** + + [Go to installation guide](How-to/installation.md) + +- :material-rocket-launch: **Run MORS** + + [Go to tutorial](Tutorials/first_run.md) + + +
+ +### Compute stellar quantities + +
+ +- :material-chart-bell-curve-cumulative: **Calculate stellar evolution** + + [Go to stellar evolution](How-to/evolution.md) + +- :fontawesome-solid-fire-alt: **Find rotation and activity quantities** + + [Go to activity quantities](How-to/activity_quantities.md) + +- :fontawesome-solid-magnifying-glass-chart: **Find evolution track quantities** + + [Go to stellar quantities](How-to/track_quantities.md) + +- :fontawesome-solid-leaf: **Find habitable zone boundaries** + + [Go to habitable zone](How-to/habitablezone.md) + +
+ +### Learn and reference + +
+ +- :material-library: **Understand the model** + + [Go to model overview](Explanations/structure.md) + +- :octicons-file-code-16: **Browse the API** + + [Go to API reference](Reference/api/index.md) + +- :material-bookshelf: **Check publications** + + [Go to publications and bibliography](Reference/publications.md) + +- :fontawesome-solid-earth: **Understand the use in PROTEUS** + + [Go to coupling to PROTEUS](Explanations/proteus.md) +
+ +### Trouble and contact + +
+ +- :fontawesome-solid-exclamation-triangle: **Troubleshoot** + + [Go to troubleshooting](How-to/troubleshooting.md) + +- :material-bug: **Raise an issue** + + [Go to issues](https://github.com/FormingWorlds/MORS/issues) + +- :material-github: **Contribute / browse code** + + [Go to source code](https://github.com/FormingWorlds/MORS) + +- :material-email: **Get in touch** + + [Go to contact](Community/contact.md) + +
\ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 415e00f..3995999 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,24 +1,22 @@ -[![Tests](https://github.com/FormingWorlds/MORS/actions/workflows/tests.yaml/badge.svg?branch=main)](https://github.com/FormingWorlds/MORS/actions/workflows/tests.yaml?query=branch%3Amain) +[![MORS CI Test Suite](https://github.com/FormingWorlds/MORS/actions/workflows/tests.yaml/badge.svg)](https://github.com/FormingWorlds/MORS/actions) ![Coverage](https://gist.githubusercontent.com/lsoucasse/a25c37a328839edd00bb32d8527aec30/raw/covbadge.svg) +[![License](https://img.shields.io/github/license/FormingWorlds/MORS?label=License)](https://github.com/FormingWorlds/MORS/blob/main/LICENSE.md) +[![PyPI](https://img.shields.io/pypi/v/fwl-mors?label=PyPI)](https://pypi.org/project/fwl-mors/) # MODEL FOR ROTATION OF STARS (MORS) -**This code is distributed as a python package for the purpose of the [PROTEUS framework](https://proteus-framework.org/PROTEUS/), a coupled simulation tool for the long-term evolution of atmospheres and interiors of rocky planets. -The MORS package solves specifically the stellar rotation and evolution. It is based on the [original code](https://www.aanda.org/articles/aa/pdf/2021/05/aa38407-20.pdf) and model developed by Colin P. Johnstone.** +**MORS** is a program designed to model stellar rotation and evolution. The package can be used to calculate evolutionary tracks for stellar rotation and X-ray, EUV, and Ly-alpha emission for stars with masses between 0.1 and 1.25 solar masses. It also allows the user to get basic stellar parameters, such as stellar radius and luminosity, as functions of mass and age using the stellar evolution models of Spada et al. ([2013](Reference/publications.md#bibliography)). -This code solves the stellar rotation and XUV evolution model presented in [Johnstone et al. (2021)](Reference/publications.md#mors-publication). The package can be used to calculate evolutionary tracks for stellar rotaton and X-ray, EUV, and Ly-alpha emission for stars with masses between 0.1 and 1.25 Msun and has additional functionality such as allowing the user to get basic stellar parameters such as stellar radius and luminosity as functions of mass and age using the stellar evolution models of [Spada et al. (2013)](Reference/publications.md#bibliography). When publishing results that were calculated using this code, both the [Johnstone et al. (2021)](Reference/publications.md#mors-publication) paper and [Spada et al. (2013)](Reference/publications.md#bibliography) should be cited. +!!! info "PROTEUS framework" + MORS is the stellar rotation and XUV evolution model integrated into the PROTEUS framework, a modular Python framework that simulates the coupled evolution of the atmospheres and interiors of rocky planets and exoplanets. The documentation for PROTEUS can be found [here](https://proteus-framework.org/PROTEUS). -!!! note - This version contains the fix for the error in the equation converting EUV1 to EUV2. +If you plan to contribute to MORS, please read our [Code of Conduct](Community/CODE_OF_CONDUCT.md). If you are running into problems, please do not hesitate to raise an [Issue](https://github.com/FormingWorlds/MORS/issues). -## Getting started -- [Installation guide](How-to/installation.md) -- [First-run tutorial](Tutorials/first_run.md) -- [Publications and bibliography](Reference/publications.md) -- [Source code](https://github.com/FormingWorlds/MORS) -- [Contact](contact.md) +!!! tip "New to MORS?" + Go to [getting started](getting_started.md) for a quick path and basic usage. -If you plan to contribute to MORS, please read our [Code of Conduct](CODE_OF_CONDUCT.md). -If you are running into problems, please do not hesitate to raise an [Issue](https://github.com/FormingWorlds/MORS/issues). \ No newline at end of file +## Citation and licence + + When publishing results that were calculated using this code, both the Johnstone et al. ([2021](Reference/publications.md#mors-publications)) paper and Spada et al. ([2013](Reference/publications.md#bibliography)) should be cited. Please also see the included [license](https://github.com/FormingWorlds/MORS/blob/main/LICENSE.md). \ No newline at end of file diff --git a/docs/proteus_framework.md b/docs/proteus_framework.md new file mode 100644 index 0000000..aa53021 --- /dev/null +++ b/docs/proteus_framework.md @@ -0,0 +1,20 @@ +

+ +
+ + +
+
+

+ +MORS is the stellar rotation and evolution module of PROTEUS (/ˈproʊtiəs, PROH-tee-əs), a modular Python framework that simulates the coupled evolution of the atmospheres and interiors of rocky planets and exoplanets. A schematic of PROTEUS components and corresponding modules can be found below. Its coupling to MORS is explained [here](Explanations/proteus.md). +
+
+You can find the documentation of each PROTEUS module in the sidebar. +
+
+ +

+
+ Schematic of PROTEUS components and corresponding modules.
+

\ No newline at end of file diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css deleted file mode 100644 index e69de29..0000000 diff --git a/docs/stylesheets/footnotes.css b/docs/stylesheets/footnotes.css new file mode 100644 index 0000000..f3729cf --- /dev/null +++ b/docs/stylesheets/footnotes.css @@ -0,0 +1,29 @@ +/* ---- Footnote/citation markers: render as inline [1] instead of superscript ---- */ +.md-typeset sup[id^="fnref"] { + vertical-align: baseline !important; + font-size: 1em !important; + line-height: inherit !important; +} + +.md-typeset sup[id^="fnref"] > a.footnote-ref { + text-decoration: none; +} + +.md-typeset sup[id^="fnref"] > a.footnote-ref::before { content: "["; } +.md-typeset sup[id^="fnref"] > a.footnote-ref::after { content: "]"; } + +/* fallback (some versions/themes) */ +.md-typeset a.footnote-ref { + vertical-align: baseline !important; + font-size: 1em !important; +} + +/* reduce space under the page title */ +.md-typeset h1 { + margin-bottom: 0.2rem; /* try 0, 0.2rem, 0.5rem */ +} + +/* optionally reduce any top margin on the first image */ +.md-typeset h1 + p img { + margin-top: 0; +} diff --git a/docs/stylesheets/proteus_theme.css b/docs/stylesheets/proteus_theme.css new file mode 100644 index 0000000..83c5b8f --- /dev/null +++ b/docs/stylesheets/proteus_theme.css @@ -0,0 +1,250 @@ +/* ========================================================= + PROTEUS theme variables + ========================================================= */ + +/* Be careful when changing these, as they affect multiple elements across the site. */ + +[data-md-color-scheme="default"], +[data-md-color-scheme="slate"] { + --md-primary-fg-color: #1c2b4b; + --md-primary-fg-color--light: #2a3d69; + --md-primary-fg-color--dark: #14203a; + + --proteus-highlight-color: #ff6e40; + --proteus-highlight-bg-soft: rgba(255, 109, 64, 0.093); + + --md-accent-fg-color: var(--proteus-highlight-color); + + --md-typeset-a-color: #3b6193; +} + +/* Softer slate background + more muted link color */ +[data-md-color-scheme="slate"] { + --md-default-bg-color: #0f172ad2; + --md-default-bg-color--light: #1e293b; + --md-default-bg-color--lighter: #334155; + --md-default-bg-color--lightest: #475569; + + --md-typeset-a-color: #8fa8c9; +} + +/* ========================================================= + Header + tabs + ========================================================= */ + +[data-md-color-scheme="default"] .md-header, +[data-md-color-scheme="default"] .md-tabs, +[data-md-color-scheme="slate"] .md-header, +[data-md-color-scheme="slate"] .md-tabs { + background-color: var(--md-primary-fg-color); +} + +[data-md-color-scheme="default"] .md-header *, +[data-md-color-scheme="default"] .md-tabs *, +[data-md-color-scheme="slate"] .md-header *, +[data-md-color-scheme="slate"] .md-tabs * { + color: #fff !important; + fill: #fff !important; +} + +/* ========================================================= + Expanded search + ========================================================= */ + +[data-md-color-scheme="default"] .md-search__form, +[data-md-color-scheme="slate"] .md-search__form { + background-color: rgba(255, 255, 255, 0.12) !important; + border-radius: 0.2rem !important; + box-shadow: none !important; +} + +[data-md-color-scheme="default"] .md-search__form:hover, +[data-md-color-scheme="default"] .md-search__form:focus-within, +[data-md-color-scheme="slate"] .md-search__form:hover, +[data-md-color-scheme="slate"] .md-search__form:focus-within { + background-color: rgba(255, 255, 255, 0.16) !important; +} + +[data-md-color-scheme="default"] .md-search__input, +[data-md-color-scheme="slate"] .md-search__input { + color: #fff !important; + -webkit-text-fill-color: #fff !important; + caret-color: #fff !important; + background: transparent !important; + -webkit-appearance: none; + appearance: none; +} + +[data-md-color-scheme="default"] .md-search__input::placeholder, +[data-md-color-scheme="slate"] .md-search__input::placeholder { + color: rgba(255, 255, 255, 0.75) !important; + -webkit-text-fill-color: rgba(255, 255, 255, 0.75) !important; + opacity: 1 !important; +} + +/* Hide browser-native search decorations */ +.md-search__input::-webkit-search-decoration, +.md-search__input::-webkit-search-cancel-button, +.md-search__input::-webkit-search-results-button, +.md-search__input::-webkit-search-results-decoration { + -webkit-appearance: none; + appearance: none; + display: none; +} + +/* Expanded search icons */ +[data-md-color-scheme="default"] .md-search__icon, +[data-md-color-scheme="default"] .md-search__icon svg, +[data-md-color-scheme="default"] .md-search__icon svg *, +[data-md-color-scheme="default"] .md-search__label, +[data-md-color-scheme="default"] .md-search__label svg, +[data-md-color-scheme="default"] .md-search__label svg *, +[data-md-color-scheme="slate"] .md-search__icon, +[data-md-color-scheme="slate"] .md-search__icon svg, +[data-md-color-scheme="slate"] .md-search__icon svg *, +[data-md-color-scheme="slate"] .md-search__label, +[data-md-color-scheme="slate"] .md-search__label svg, +[data-md-color-scheme="slate"] .md-search__label svg * { + color: #fff !important; + fill: #fff !important; + stroke: #fff !important; + opacity: 1 !important; +} + +/* ========================================================= + Collapsed search trigger in header + ========================================================= */ + +[data-md-color-scheme="default"] .md-search__button, +[data-md-color-scheme="slate"] .md-search__button { + color: #fff !important; + background-color: rgba(255, 255, 255, 0.12) !important; + border-radius: 0.6rem !important; +} + +[data-md-color-scheme="default"] .md-search__button:hover, +[data-md-color-scheme="default"] .md-search__button:focus, +[data-md-color-scheme="slate"] .md-search__button:hover, +[data-md-color-scheme="slate"] .md-search__button:focus { + background-color: rgba(255, 255, 255, 0.16) !important; +} + +/* Collapsed magnifier */ +[data-md-color-scheme="default"] .md-search__button::before, +[data-md-color-scheme="slate"] .md-search__button::before { + color: #fff !important; + -webkit-text-fill-color: #fff !important; + filter: brightness(0) invert(1) !important; + opacity: 1 !important; +} + +/* If an inner SVG is used in some states */ +[data-md-color-scheme="default"] .md-search__button svg, +[data-md-color-scheme="default"] .md-search__button svg *, +[data-md-color-scheme="slate"] .md-search__button svg, +[data-md-color-scheme="slate"] .md-search__button svg * { + fill: #fff !important; + stroke: #fff !important; + color: #fff !important; +} + +/* Shortcut badge */ +[data-md-color-scheme="default"] .md-search__button kbd, +[data-md-color-scheme="default"] .md-search__button .md-search__kbd, +[data-md-color-scheme="slate"] .md-search__button kbd, +[data-md-color-scheme="slate"] .md-search__button .md-search__kbd { + color: rgba(255, 255, 255, 0.9) !important; + background-color: rgba(255, 255, 255, 0.18) !important; + border: none !important; + box-shadow: none !important; +} + +/* Badge drawn as pseudo-element in some versions */ +[data-md-color-scheme="default"] .md-search__button::after, +[data-md-color-scheme="slate"] .md-search__button::after { + background-color: rgba(255, 255, 255, 0.12) !important; + border: none !important; + box-shadow: none !important; + color: rgba(255, 255, 255, 0.9) !important; +} + +/* ========================================================= + Top tabs + ========================================================= */ + +/* All tab labels white by default */ +[data-md-color-scheme="default"] .md-tabs__link, +[data-md-color-scheme="slate"] .md-tabs__link { + color: #fff !important; + opacity: 0.9 !important; + transition: color 0.15s ease, opacity 0.15s ease !important; +} + +/* Hover state */ +[data-md-color-scheme="default"] .md-tabs__link:hover, +[data-md-color-scheme="slate"] .md-tabs__link:hover { + color: var(--proteus-highlight-color) !important; + opacity: 0.8 !important; +} + +/* Active tab text only, no underline */ +[data-md-color-scheme="default"] .md-tabs__item--active, +[data-md-color-scheme="default"] .md-tabs__link--active, +[data-md-color-scheme="default"] .md-tabs__item--active .md-tabs__link, +[data-md-color-scheme="slate"] .md-tabs__item--active, +[data-md-color-scheme="slate"] .md-tabs__link--active, +[data-md-color-scheme="slate"] .md-tabs__item--active .md-tabs__link { + color: var(--proteus-highlight-color) !important; + box-shadow: none !important; + border-bottom: none !important; + text-decoration: none !important; +} + +/* Remove any underline/pseudo-element indicator */ +[data-md-color-scheme="default"] .md-tabs__item--active::after, +[data-md-color-scheme="default"] .md-tabs__link--active::after, +[data-md-color-scheme="default"] .md-tabs__item--active .md-tabs__link::after, +[data-md-color-scheme="slate"] .md-tabs__item--active::after, +[data-md-color-scheme="slate"] .md-tabs__link--active::after, +[data-md-color-scheme="slate"] .md-tabs__item--active .md-tabs__link::after { + content: none !important; + display: none !important; + background: none !important; +} + +/* ========================================================= + Sidebar navigation + ========================================================= */ + +/* Style only active leaf page links */ +[data-md-color-scheme="default"] .md-nav__item .md-nav__link--active:not(.md-nav__link--passed), +[data-md-color-scheme="slate"] .md-nav__item .md-nav__link--active:not(.md-nav__link--passed) { + background-color: var(--proteus-highlight-bg-soft) !important; + border-radius: 1rem !important; + color: var(--proteus-highlight-color) !important; + padding-left: 1rem; + padding-right: 1rem; +} + +/* ========================================================= + Footer + ========================================================= */ + +/* Remove underline from footer copyright link */ +.md-footer-copyright a, +.md-footer-meta a { + text-decoration: none !important; +} + +/* ========================================================= + Make header title look clickable and add hover effect + ========================================================= */ + +.md-header__title[data-md-component="header-title"] { + cursor: pointer; + transition: opacity 0.15s ease !important; +} + +.md-header__title[data-md-component="header-title"]:hover { + opacity: 0.7 !important; +} diff --git a/mkdocs.yml b/mkdocs.yml index 93920a9..80c4bc0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -3,36 +3,71 @@ site_url: https://proteus-framework.org/MORS/ repo_url: https://github.com/FormingWorlds/MORS repo_name: GitHub +copyright: '© 2023-2026 Forming Worlds Lab' + nav: - - Home: index.md + - Home: + - About: index.md + - Getting started: getting_started.md - How-to guides: - Installation: How-to/installation.md - - Calculate stellar evolution: How-to/evolution.md - - Calculate cluster evolution: How-to/clusters.md - - Using model percentiles for initial rotation: How-to/distribution_percentile.md - - Setting custom simulation parameters: How-to/set_parameters.md - - Find stellar rotation and activity quantities: How-to/activity_quantities.md - - Find stellar quantities with stellar evolution tracks: How-to/track_quantities.md - - Find habitable zone boundaries: How-to/habitablezone.md - - Find stellar activity timelines: How-to/activity_timelines.md + - Basic usage: + - Calculate stellar evolution: How-to/evolution.md + - Find stellar quantities with stellar evolution tracks: How-to/track_quantities.md + - Find stellar rotation and activity quantities: How-to/activity_quantities.md + - Find habitable zone boundaries: How-to/habitablezone.md + - Using model percentiles for initial rotation: How-to/distribution_percentile.md + - Advanced usage: + - Calculate cluster evolution: How-to/clusters.md + - Setting custom simulation parameters: How-to/set_parameters.md + - Find stellar activity timelines: How-to/activity_timelines.md + - Development: + - Testing: How-to/run_tests.md + - Build documentation: How-to/documentation.md + - Troubleshooting: How-to/troubleshooting.md - Tutorials: - First run: Tutorials/first_run.md + - Explanations: + - Model overview: + - Introduction: Explanations/structure.md + - Rotational evolution model: Explanations/rotation.md + - High-energy emission and activity model: Explanations/activity.md + - Habitable zone boundaries: Explanations/habitablezone.md + - Spectral synthesis: Explanations/spectral_synthesis.md + - Coupling to PROTEUS: Explanations/proteus.md + - Reference: - Publications and bibliography: Reference/publications.md + - Parameter reference: Reference/parameters.md + - API reference: + - Overview: Reference/api/index.md + - mors.star: Reference/api/star.md + - mors.cluster: Reference/api/cluster.md + - mors.physicalmodel: Reference/api/physicalmodel.md + - mors.rotevo: Reference/api/rotevo.md + - mors.stellarevo: Reference/api/stellarevo.md + - mors.baraffe: Reference/api/baraffe.md + - mors.spectrum: Reference/api/spectrum.md + - mors.synthesis: Reference/api/synthesis.md + - mors.miscellaneous: Reference/api/miscellaneous.md + - mors.parameters: Reference/api/parameters.md + - mors.constants: Reference/api/constants.md + - mors.data: Reference/api/data.md + - mors.cli: Reference/api/cli.md - Community: - - Contributing: CONTRIBUTING.md - - Code of Conduct: CODE_OF_CONDUCT.md + - Contact: Community/contact.md + - Contributing: Community/CONTRIBUTING.md + - Code of Conduct: Community/CODE_OF_CONDUCT.md - Developers: https://proteus-framework.org/people - - Contact: contact.md - Source code: https://github.com/FormingWorlds/MORS - - Issues page: https://github.com/FormingWorlds/MORS/issues - Other PROTEUS modules: + - PROTEUS framework: proteus_framework.md - 🔗 PROTEUS: https://proteus-framework.org/PROTEUS/ - 🔗 JANUS: https://proteus-framework.org/JANUS/ - 🔗 ZEPHYRUS: https://proteus-framework.org/ZEPHYRUS/ @@ -46,13 +81,34 @@ nav: - 🔗 Atmodeller: https://atmodeller.readthedocs.io/en/latest/ - 🔗 FastChem: https://newstrangeworlds.github.io/FastChem/ - 🔗 PLATON: https://platon.readthedocs.io/en/latest/ + - 🔗 SOCRATES: https://github.com/FormingWorlds/SOCRATES/ + +# footer: +extra: + generator: false + social: + - icon: material/web + link: https://proteus-framework.org/ + name: PROTEUS website + - icon: material/email + link: mailto:proteus_dev@formingworlds.space + name: Mail PROTEUS developers + - icon: fontawesome/brands/python + link: https://pypi.org/project/fwl-mors/ + name: MORS on PyPI + - icon: fontawesome/brands/github + link: https://github.com/FormingWorlds/MORS + name: MORS on GitHub theme: name: material custom_dir: docs/overrides features: - - content.code.copy - - navigation.expand + - navigation.tabs + - navigation.tabs.sticky + - navigation.expand + - content.code.copy + - content.tabs.link # Default assets (used unless overridden per palette below) favicon: assets/PROTEUS_black_on_white_logo_only.png @@ -67,7 +123,7 @@ theme: favicon: assets/PROTEUS_black_on_white_logo_only.png logo: assets/PROTEUS_white_on_black.png toggle: - icon: material/weather-night + icon: lucide/moon name: Switch to dark mode - media: "(prefers-color-scheme: dark)" @@ -77,11 +133,12 @@ theme: favicon: assets/PROTEUS_white_on_black_logo_only.png logo: assets/PROTEUS_white_on_black.png toggle: - icon: material/weather-sunny + icon: lucide/sun name: Switch to light mode extra_css: - - stylesheets/extra.css + - stylesheets/proteus_theme.css + - stylesheets/footnotes.css markdown_extensions: - admonition @@ -91,7 +148,13 @@ markdown_extensions: - pymdownx.extra - pymdownx.arithmatex: generic: true + - pymdownx.tabbed: + alternate_style: true - footnotes + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + extra_javascript: - https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js @@ -122,7 +185,7 @@ plugins: show_root_toc_entry: true show_docstring_attributes: true show_root_full_path: false - docstring_section_style: list + docstring_section_style: table members_order: alphabetical merge_init_into_class: yes filters: ["!^_"] diff --git a/pyproject.toml b/pyproject.toml index b9374f7..93d0c88 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,8 +9,10 @@ description = "Stellar rotation and activity evolution model" readme = "README.md" authors = [ {name = "Colin P. Johnstone", email = "colinjohnstone@gmail.com" }, - {name = "Harrison Nicholls", email = "harrison.nicholls@physics.ox.ac.uk"}, - {name = "Laurent Soucasse", email = "l.soucasse@esciencecenter.nl"} + {name = "Harrison Nicholls", email = "contact@h-nicholls.space"}, + {name = "Laurent Soucasse", email = "l.soucasse@esciencecenter.nl"}, + {name = "Tim Lichtenberg", email = "tim.lichtenberg@rug.nl"}, + {name = "Karen Stuitje", email = "stuitje@proton.me"} ] license = {text = "MIT License"} classifiers = [ @@ -43,13 +45,16 @@ develop = [ "bump-my-version", "coverage[toml]", "pip-tools", - "pytest" + "pytest", + "pytest-cov" ] docs = [ - "markdown-include", - "mkdocs", - "mkdocs-material", - "mkdocstrings[python]", + "zensical", + "mkdocs-material", + "markdown-include", + "mkdocs", + "mkdocstrings[python]", + "pymdown-extensions" ] publishing = [ "twine", diff --git a/src/mors/baraffe.py b/src/mors/baraffe.py index afd7c0f..296daa1 100644 --- a/src/mors/baraffe.py +++ b/src/mors/baraffe.py @@ -91,13 +91,13 @@ def BaraffeLuminosity(self, tstar): Parameters ---------- - tstar : float - Star's age [yr] + tstar : float + Star's age [yr] Returns - ---------- - Lstar : float - Luminosity Flux in units of solar luminosity + ------- + Lstar : float + Luminosity Flux in units of solar luminosity """ # Get time and check that it is in range @@ -122,15 +122,15 @@ def BaraffeSolarConstant(self, tstar, mean_distance): Parameters ---------- - tstar : float - Star's age [yr] - mean_distance : float - Star-planet distance [AU] + tstar : float + Star's age [yr] + mean_distance : float + Star-planet distance [AU] Returns - ---------- - inst : float - Flux at planet's orbital separation (solar constant) in W/m^2 + ------- + inst : float + Flux at planet's orbital separation (solar constant) in W/m^2 """ Lstar = self.BaraffeLuminosity(tstar) @@ -146,13 +146,13 @@ def BaraffeStellarRadius(self, tstar): Parameters ---------- - tstar : float - Star's age [yr] + tstar : float + Star's age [yr] Returns - ---------- - Rstar : float - Radius of star in units of solar radius + ------- + Rstar : float + Radius of star in units of solar radius """ # Get time and check that it is in range @@ -174,13 +174,13 @@ def BaraffeStellarTeff(self, tstar): Parameters ---------- - tstar : float - Star's age [yr] + tstar : float + Star's age [yr] Returns - ---------- - Teff : float - Temperature of star [K] + ------- + Teff : float + Temperature of star [K] """ # Get time and check that it is in range @@ -202,16 +202,17 @@ def BaraffeSpectrumCalc(self, tstar: float, Lstar_modern: float, spec_fl: list): Parameters ---------- - tstar : float - Star's age [yr] - Lstar_modern : float - Modern star luminosity in units of solar luminosity - spec_fl : list - Modern spectrum flux array at 1 AU + tstar : float + Star's age [yr] + Lstar_modern : float + Modern star luminosity in units of solar luminosity + spec_fl : list + Modern spectrum flux array at 1 AU + Returns - ---------- - hspec_fl : np.array(float) - Numpy array of flux at 1 AU + ------- + hspec_fl : np.array(float) + Numpy array of flux at 1 AU """ # Get luminosity data from track @@ -227,20 +228,20 @@ def BaraffeLoadTrack(Mstar, pre_interp = True, tmin = None, tmax = None): Parameters ---------- - Mstar : float - Star mass (in unit of solar mass) - It assumes the value has been checked and matches with the mass grid. - pre_interp : bool - Pre-interpolate the tracks onto a higher resolution time-grid - tmin : float - Minimum value of the temporal grid - tmax : float - Maximum value of the temporal grid + Mstar : float + Star mass (in unit of solar mass) + It assumes the value has been checked and matches with the mass grid. + pre_interp : bool + Pre-interpolate the tracks onto a higher resolution time-grid + tmin : float + Minimum value of the temporal grid + tmax : float + Maximum value of the temporal grid Returns - ---------- - track : dict - Dictionary containing track data + ------- + track : dict + Dictionary containing track data """ # Load data @@ -304,17 +305,17 @@ def ModernSpectrumLoad(input_spec_file: str, output_spec_file: str): Parameters ---------- - input_spec_file : str - Path to input spectral file - output_spec_file : str - Path to copied spectral file + input_spec_file : str + Path to input spectral file + output_spec_file : str + Path to copied spectral file Returns - ---------- - spec_wl : np.array[float] - Wavelength [nm] - spec_fl : np.array[float] - Flux [erg s-1 cm-2 nm-1] + ------- + spec_wl : np.array[float] + Wavelength [nm] + spec_fl : np.array[float] + Flux [erg s-1 cm-2 nm-1] """ if os.path.isfile(input_spec_file): @@ -336,4 +337,4 @@ def ModernSpectrumLoad(input_spec_file: str, output_spec_file: str): #log.debug("\t Wavelength \n\t\t (min, max) = (%1.2e, %1.2e) nm" % (np.amin(spec_wl),np.amax(spec_wl))) #log.debug("\t Bin width \n\t\t (min, median, max) = (%1.2e, %1.2e, %1.2e) nm" % (np.amin(binwidth_wl),np.median(binwidth_wl),np.amax(binwidth_wl))) - return spec_wl, spec_fl + return spec_wl, spec_fl \ No newline at end of file diff --git a/src/mors/cluster.py b/src/mors/cluster.py index ed92446..206b480 100644 --- a/src/mors/cluster.py +++ b/src/mors/cluster.py @@ -20,13 +20,6 @@ class Cluster: """A class for star objects that hold all information about a star. - - Attributes - ------------ - - Methods - ------------ - """ def __init__(self,Mstar=None,Age=None,Omega=None,OmegaEnv=None,OmegaCore=None,AgesOut=None,starEvoDir=None,evoModels=None,params=params.paramsDefault,verbose=False): diff --git a/src/mors/logs.py b/src/mors/logs.py index b8797cc..3a15981 100644 --- a/src/mors/logs.py +++ b/src/mors/logs.py @@ -4,13 +4,13 @@ # Simple terminal-logger instance def setup_logger(level:str="INFO"): - custom_logger = logging.getLogger("fwl."+__name__) + custom_logger = logging.getLogger("fwl") custom_logger.handlers.clear() level = str(level).strip().upper() if level not in ["INFO", "DEBUG", "ERROR", "WARNING"]: raise ValueError("Invalid log level '%s'"%level) - level_code = logging.getLevelName(level) + level_code = getattr(logging, level) # Add terminal output to logger sh = logging.StreamHandler(sys.stdout) diff --git a/src/mors/physicalmodel.py b/src/mors/physicalmodel.py index f54c81b..1b8ba57 100644 --- a/src/mors/physicalmodel.py +++ b/src/mors/physicalmodel.py @@ -1,4 +1,3 @@ - """Module for holding functions for calculating physical quantities for the physical rotation and activity model.""" # Imports for standard stuff needed here @@ -36,7 +35,7 @@ def dOmegadt(Mstar=None,Age=None,OmegaEnv=None,OmegaCore=None,params=params.para Instance of StarEvo class holding stellar evolution model data. Returns - ---------- + ------- dOmegaEnvdt : float Rate of change of envelope rotation rate in OmegaSun Myr^-1. dOmegaCoredt : float @@ -87,7 +86,7 @@ def RotationQuantities(Mstar=None,Age=None,OmegaEnv=None,OmegaCore=None,params=p Instance of StarEvo class holding stellar evolution model data. Returns - ---------- + ------- StarState : dict Set of rotation quantities. @@ -259,7 +258,7 @@ def ExtendedQuantities(StarState=None,Mstar=None,Age=None,OmegaEnv=None,OmegaCor Instance of StarEvo class holding stellar evolution model data. Returns - ---------- + ------- StarState : dict Set of extended quantities. @@ -334,7 +333,7 @@ def QuantitiesUnits(StarState=None): Set of quantities already calculated by RotationQuantities() and possibly ExtendedQuantities(). Returns - ---------- + ------- StarStateUnits : dict Dictionary of strings holding units for quantities. @@ -433,8 +432,6 @@ def Lxuv(Mstar=None,Age=None,Omega=None,OmegaEnv=None,Prot=None,params=params.pa Parameters ---------- - Band : str - Wavelength band to calculate. Mstar : float Mass of star in Msun. Age : float @@ -451,9 +448,9 @@ def Lxuv(Mstar=None,Age=None,Omega=None,OmegaEnv=None,Prot=None,params=params.pa Instance of StarEvo class holding stellar evolution model data. Returns - ---------- + ------- LxuvDict : dict - Dictionary of luminositie in erg s^-1. + Dictionary of luminosities in erg s^-1. """ @@ -560,7 +557,7 @@ def Lx(Mstar=None,Age=None,Omega=None,OmegaEnv=None,Prot=None,params=params.para Instance of StarEvo class holding stellar evolution model data. Returns - ---------- + ------- Lx : float X-ray luminosity in erg s^-1. @@ -606,7 +603,7 @@ def XrayScatter(XrayAverage,params=params.paramsDefault): Dictionary holding model parameters. Returns - ---------- + ------- deltaXray : float or numpy.ndarray Values for stellar deltaLx, deltaFx, or deltaRx in input units. @@ -648,7 +645,7 @@ def XUVScatter(XUVAverage,params=params.paramsDefault): Dictionary holding model parameters. Returns - ---------- + ------- deltaXUV : dict Values for deltaLx, deltaFx, etc. for all quantities calculated by Lxuv(). @@ -754,7 +751,7 @@ def Leuv(Mstar=None,Age=None,Omega=None,OmegaEnv=None,Prot=None,band=0,params=pa Instance of StarEvo class holding stellar evolution model data. Returns - ---------- + ------- Leuv : float EUV luminosity in erg s^-1. @@ -840,7 +837,7 @@ def Lly(Mstar=None,Age=None,Omega=None,OmegaEnv=None,Prot=None,params=params.par Instance of StarEvo class holding stellar evolution model data. Returns - ---------- + ------- Lly : float Lyman-alpha luminosity in erg s^-1. @@ -1032,7 +1029,7 @@ def OmegaBreak(Mstar,Rstar): Radius of star in Rsun. Returns - ---------- + ------- OmegaBreak : float Breakup angular velocity in OmegaSun. @@ -1052,7 +1049,7 @@ def _vEsc(Mstar,Rstar): Radius of star in Rsun. Returns - ---------- + ------- vEsc : float Surface escape velocity in cm s^-1. @@ -1108,7 +1105,7 @@ def OmegaSat(Mstar=None,Age=None,param='XUV',params=params.paramsDefault,StarEvo Instance of StarEvo class holding stellar evolution model data. Returns - ---------- + ------- Omega : float Saturation rotation rate in OmegaSun. @@ -1151,7 +1148,7 @@ def ProtSat(Mstar=None,Age=None,param='XUV',params=params.paramsDefault,StarEvo= Instance of StarEvo class holding stellar evolution model data. Returns - ---------- + ------- Prot : float Saturation rotation period in days. @@ -1200,8 +1197,9 @@ def aOrbHZ(Mstar=None,Age=None,params=params.paramsDefault): Age to get stellar parameters in Myr (default = 5000 Myr). params : dict , optional Dictionary holding model parameters. + Returns - ---------- + ------- aOrbHZAll : dict Values of HZ boundary orbital distances in AU. """ diff --git a/src/mors/spectrum.py b/src/mors/spectrum.py index 983359c..42e243e 100644 --- a/src/mors/spectrum.py +++ b/src/mors/spectrum.py @@ -28,15 +28,15 @@ def WhichBand(wl:float): Parameters ---------- - wl : float - Wavelength to query [nm] + wl : float + Wavelength to query [nm] Returns - ---------- - bands : list | None - List of band names (strings) which this band is inside of. Bands - can overlap, so this list may have a length greater than one. - If `wl` is outside all bandpasses, then this value is None. + ------- + bands : list | None + List of band names (strings) which this band is inside of. Bands + can overlap, so this list may have a length greater than one. + If `wl` is outside all bandpasses, then this value is None. """ @@ -123,10 +123,10 @@ def LoadDirectly(self, spec_wl:np.ndarray, spec_fl:np.ndarray): Parameters ---------- - spec_wl : np.ndarray - Array of wavelengths [nm] - spec_fl : np.ndarray - Array of fluxes [erg s-1 cm-2 nm-1] + spec_wl : np.ndarray + Array of wavelengths [nm] + spec_fl : np.ndarray + Array of fluxes [erg s-1 cm-2 nm-1] """ @@ -169,8 +169,8 @@ def LoadTSV(self, fp:str): Parameters ---------- - fp : str - Path to file + fp : str + Path to file """ log.debug("Loading stellar spectrum from TSV file") @@ -202,8 +202,8 @@ def ExtendShortwave(self, wl_min:float): Parameters ---------- - wl_min : float - New minimum wavelength [nm] + wl_min : float + New minimum wavelength [nm] """ # Already extended @@ -230,12 +230,12 @@ def ExtendPlanck(self, Teff:float, R_star:float, wl_max:float): Parameters ---------- - Teff : float - Effective temperature of star - R_star : float - Radius of star [m] - wl_max : float - New maximum wavelength [nm] + Teff : float + Effective temperature of star + R_star : float + Radius of star [m] + wl_max : float + New maximum wavelength [nm] """ @@ -266,8 +266,8 @@ def WriteTSV(self, fp:str): Parameters ---------- - fp : str - Path to file + fp : str + Path to file """ log.debug("Writing stellar spectrum to TSV file") @@ -286,15 +286,15 @@ def PlanckFunction_surf(wl:np.ndarray, Teff:float): Parameters ---------- - wl : np.ndarray - Wavelength array [nm] - Teff : float - Effective temperature of the object + wl : np.ndarray + Wavelength array [nm] + Teff : float + Effective temperature of the object Returns - ---------- - yp : float - Flux at stellar surface [erg s-1 cm-2 nm-1] + ------- + yp : float + Flux at stellar surface [erg s-1 cm-2 nm-1] """ yp = np.zeros(np.shape(wl)) @@ -317,15 +317,15 @@ def ScaleToSurf(fl:np.ndarray, R_star:float): Parameters ---------- - fl : np.ndarray - Spectrum at 1 AU - R_star : float - Star radius in [m] + fl : np.ndarray + Spectrum at 1 AU + R_star : float + Star radius in [m] Returns - ---------- - fl_surf : np.ndarray - Flux at stellar surface (same units as `fl`) + ------- + fl_surf : np.ndarray + Flux at stellar surface (same units as `fl`) """ return fl * (const.AU_SI/R_star)**2 @@ -336,16 +336,15 @@ def ScaleTo1AU(fl:np.ndarray, R_star:float): Parameters ---------- - fl : np.ndarray - Spectrum at surface - R_star : float - Star radius in [m] + fl : np.ndarray + Spectrum at surface + R_star : float + Star radius in [m] Returns - ---------- - fl_1AU : np.ndarray - Flux at 1 AU (same units as `fl`) + ------- + fl_1AU : np.ndarray + Flux at 1 AU (same units as `fl`) """ return fl * (R_star/const.AU_SI)**2 - diff --git a/src/mors/star.py b/src/mors/star.py index 01ddf0e..a6c89ea 100644 --- a/src/mors/star.py +++ b/src/mors/star.py @@ -23,13 +23,6 @@ class Star: """A class for star objects that hold all information about a star. - - Attributes - ------------ - - Methods - ------------ - """ #--------------------------------------------------------------------------------------- @@ -38,14 +31,18 @@ def __init__(self,Mstar=None,Age=None,percentile=None,Omega=None,Prot=None,Omega """Initialises instance of Star class. This is the main function that is run when creating an instance of the Star class and it sets up all - the things needed including calculating evolutionary tracks for the star. The function requires that - the arguments Mstar (the star's mass in Msun) and Omega (in OmegaSun=2.67e-6 rad s^-1) are specified + the things needed including calculating evolutionary tracks for the star. + + The function requires that the arguments Mstar (the star's mass in Msun) and Omega (in OmegaSun=2.67e-6 rad s^-1) are specified in the call. Alternatively, OmegaEnv and OmegaCore can be set, in which case Omega does not need to be specified. If the argument Age (in Myr) is also specified, then the code will find the evolutionary track that passes through this rotation rate at this age, otherwise if Age is not set then it will - calculate evolutionary tracks assuming this Omega as the initial (1 Myr) rotation rate. The user should - not specify OmegaCore and Age simultaneously, and if Age is set then either Omega or OmegaEnv can be - used to specify the surface rotation rate. The user can also specify the initial rotation rate using + calculate evolutionary tracks assuming this Omega as the initial (1 Myr) rotation rate. + + The user should not specify OmegaCore and Age simultaneously, and if Age is set then either Omega or OmegaEnv can be + used to specify the surface rotation rate. + + The user can also specify the initial rotation rate using using a percentile of the 1 Myr rotation distribution via the ``percentile`` keyword argument. Parameters ---------- diff --git a/src/mors/stellarevo.py b/src/mors/stellarevo.py index 8def55c..fee7dc4 100644 --- a/src/mors/stellarevo.py +++ b/src/mors/stellarevo.py @@ -1,4 +1,3 @@ - """Module for loading the stellar evolution tracks and retrieving basic stellar properties.""" import copy @@ -36,13 +35,10 @@ class StarEvo: single stellar mass and holds evolutionary tracks for a bunch of important quantities. Attributes - ------------ + ---------- ModelData : dict This is a dictionary of dictionaries, holding evolution tracks for each stellar mass. - Methods - ------------ - """ def __init__(self,starEvoDir=starEvoDirDefault,evoModels=evoModelsDefault): @@ -73,7 +69,7 @@ def LoadTrack(self,Mstar,ClearData=False): If set to True, all other stellar masses will be removed from the ModelData dictionary. Returns - ---------- + ------- None None @@ -88,23 +84,23 @@ def Value(self, Mstar,Age,ParamString): The set of models should have already been loaded. With this function, the user can ask for a value of one of the parameters for a specific stellar mass and age. All three of these can be input as multiple - values and an array of values will be returned if this is the case. For example, if MstarIn is input as + values and an array of values will be returned if this is the case. For example, if Mstar is input as a 1D array, the function will return a 1D array giving the value for each of these masses. ParamString can be input as a list of strings and values for each parameter in that list will be returned in a 1D array. If two are given as arrays or lists, then a 2D array will be returned. If all three then a 3D - array with dimensions len(MstarIn)xlen(AgeIn)xlen(ParamString) will be returned. + array with dimensions len(Mstar)xlen(Age)xlen(ParamString) will be returned. Parameters ---------- - MstarIn : float or int or numpy.ndarray + Mstar : float or int or numpy.ndarray Mass of star in Msun. - AgeIn : float or int or numpy.ndarray + Age : float or int or numpy.ndarray Age in Myr. ParamString : str String holding name of parameter to get value for. Returns - ---------- + ------- value : float or numpy.ndarray Value of parameter at this mass and age. @@ -441,9 +437,9 @@ def LoadTrack(Mstar,ModelData=None,ClearData=False): If set to True, all other stellar masses will be removed from the ModelData dictionary. Returns - ---------- - None - None + ------- + ModelData : dict + Updated dictionary of stellar evolution models. """ # Use default model data if nothing was specified @@ -585,7 +581,7 @@ def Value(MstarIn,AgeIn,ParamString,ModelData=ModelDataDefault): Dictionary of dictionaries holding set of stellar evolution models. Returns - ---------- + ------- value : float or numpy.ndarray Value of parameter at this mass and age. diff --git a/src/mors/synthesis.py b/src/mors/synthesis.py index ffbb2e7..9ace974 100644 --- a/src/mors/synthesis.py +++ b/src/mors/synthesis.py @@ -23,17 +23,17 @@ def GetProperties(Mstar:float, pctle:float, age:float): Parameters ---------- - Mstar : float - Mass of star [M_sun] - pctle : float - Rotation percentile - age : float - Stellar age [Myr] + Mstar : float + Mass of star [M_sun] + pctle : float + Rotation percentile + age : float + Stellar age [Myr] Returns - ---------- - out : dict - Dictionary of radius [m], Teff [K], and band fluxes at 1 AU [erg s-1 cm-2] + ------- + out : dict + Dictionary of radius [m], Teff [K], and band fluxes at 1 AU [erg s-1 cm-2] """ # Get star radius [m] @@ -85,15 +85,15 @@ def CalcBandScales(modern_dict:dict, historical_dict): Parameters ---------- - modern_dict : dict - Dictionary output of `GetProperties` call for modern spectrum - historical_dict : dict - Dictionary output of `GetProperties` call for historical spectrum + modern_dict : dict + Dictionary output of `GetProperties` call for modern spectrum + historical_dict : dict + Dictionary output of `GetProperties` call for historical spectrum Returns - ---------- - Q_dict : dict - Dictionary of band scale factors + ------- + Q_dict : dict + Dictionary of band scale factors """ # Get scale factors @@ -110,17 +110,17 @@ def CalcScaledSpectrumFromProps(modern_spec:spec.Spectrum, modern_dict:dict, his Parameters ---------- - modern_spec : Spectrum object - Spectrum object containing data for a modern fluxes - modern_dict : dict - Dictionary output of `GetProperties` call for modern spectrum - historical_dict : dict - Dictionary output of `GetProperties` call for historical spectrum + modern_spec : Spectrum object + Spectrum object containing data for a modern fluxes + modern_dict : dict + Dictionary output of `GetProperties` call for modern spectrum + historical_dict : dict + Dictionary output of `GetProperties` call for historical spectrum Returns - ---------- - historical_spec : Spectrum object - Spectrum object containing data for historical fluxes + ------- + historical_spec : Spectrum object + Spectrum object containing data for historical fluxes """ log.debug("Calculating scaled spectrum from properties") @@ -151,19 +151,19 @@ def FitModernProperties(modern_spec:spec.Spectrum, Mstar:float, age:float=-1): Parameters ---------- - modern_spec : Spectrum object - Spectrum object containing data for a modern fluxes - Mstar : float - Stellar mass [M_sun] - age : float - Optional guess for current age. Will be estimated if not provided. + modern_spec : Spectrum object + Spectrum object containing data for a modern fluxes + Mstar : float + Stellar mass [M_sun] + age : float + Optional guess for current age. Will be estimated if not provided. Returns - ---------- - best_pctle : float - Best estimate of rotation percentile - best_age : float - Best estimate of star's age (equal to `age` if `age` is provided) + ------- + best_pctle : float + Best estimate of rotation percentile + best_age : float + Best estimate of star's age (equal to `age` if `age` is provided) """ log.debug("Fitting properties to modern spectrum") @@ -216,5 +216,3 @@ def _fev(x_arr:tuple): # Return return best_pctle, best_age - -